Sorting & section in UItableview from JSON data - ios

I have defined the data model for packages data, but need to define UITableview sections on the basis of
subscriptiontype = 'Yearly', 'Monthly', 'Weekly'
Getting an error of - Cannot assign value of type '[Package]' to type '[[String : String]]?'. How can I assign it to tableview sections.
Code:
var packag = [Package]()
enum TableSection: Int {
case subscriptionType = 0, yearly, monthly, weekly, total
}
var data = [TableSection: [[String: String]]]()
func sortData() {
data[.yearly] = packag.filter({ $0.subscriptionType == "yearly" })
data[.monthly] = packag.filter({ $0.subscriptionType == "monthly" })
data[.weekly] = packag.filter({ $0.subscriptionType == "weekly" })
}
Updated Code - viewdidload():
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
//fetchData()
if let path = Bundle.main.path(forResource: "packageList", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
return Date(timeIntervalSince1970: TimeInterval(dateStr)!)
}
let jSON = try? decoder.decode(Root.self, from: data)
if let packages = jSON?.packages {
self.sortData(packages: packages)
print(packages)
}
} catch {
// handle error
print(Error.self)
}
}
}
Root Model:
struct Root : Codable {
let packages : [Package]
}
Packages Model:
struct Package : Codable {
let availableUntil : Date
let benefits : [String]?
let desc : String
let didUseBefore : Bool
let name : String
let price : Double
let subscriptionType : String
let tariff : Tariff
}
Traiff Model:
struct Tariff : Codable {
let data : String
let sms : String
let talk : String
}
Updated PackageJson Data:
{ "packages": [
{
"name": "Platinum Maksi 6 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": true,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "6144",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1558131150"
},
{
"name": "Platinum Maksi 8 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": false,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 129.90,
"tariff": {
"data": "8192",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1555060350"
},
{
"name": "Platinum Maksi 12 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "yearly",
"didUseBefore": false,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "12288",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1555060350"
},

The problem is that you are trying to assign a Package object to a value that is expecting an array of dictionary.
Your data variable is a dictionary that has a TableSection as a key, and an array of dictionaries as the value which you have defined by writing [[String: String]]. Then in your sortData function you're trying to assign a value to various data keys, but you're assigning a Package item to it when it's expecting an array of dictionaries.
What will work is if you change your data definition to
var data = [TableSection: Package]()

Model
import Foundation
struct JSON: Codable {
let packages: [Package]
}
struct Package: Codable {
let name, desc, subscriptionType: String
let didUseBefore: Bool
let benefits: [String]
let price: Double
let tariff: Tariff
let availableUntil: String
}
struct Tariff: Codable {
let data, talk, sms: String
}
Enum
enum TableSection: Int {
case subscriptionType = 0, yearly, monthly, weekly, total
}
Data
var data = [TableSection: [Package]]()
Parsing
if let path = Bundle.main.path(forResource: "document", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jSON = try? JSONDecoder().decode(JSON.self, from: data)
if let packages = jSON?.packages {
self.sortData(packages: packages)
}
} catch {
// handle error
}
}
Sorting
private func sortData(packages : [Package]) {
data[.yearly] = packages.filter({ $0.subscriptionType == "yearly" })
data[.monthly] = packages.filter({ $0.subscriptionType == "monthly" })
data[.weekly] = packages.filter({ $0.subscriptionType == "weekly" })
print("Data is \(data)")
}
I use LOCAL JSON, you can use server data.

Related

Custom JSON decoding: decode an array of different objects in several distinct arrays

I need your help to implement a custom JSON decoding. The JSON returned by the API is:
{
"zones": [
{
"name": "zoneA",
"blocks": [
// an array of objects of type ElementA
]
},
{
"name": "zoneB",
"blocks": [
// an array of objects of type ElementB
]
},
{
"name": "zoneC",
"blocks": [
// an array of objects of type ElementC
]
},
{
"name": "zoneD",
"blocks": [
// an array of objects of type ElementD
]
}
]
}
I don't want to parse this JSON as an array of zones with no meaning. I'd like to produce a model with an array for every specific type of block, like this:
struct Root {
let elementsA: [ElementA]
let elementsB: [ElementB]
let elementsC: [ElementC]
let elementsD: [ElementD]
}
How can I implement the Decodable protocol (by using init(from decoder:)) to follow this logic? Thank you.
This is a solution with nested containers. With the given (simplified but valid) JSON string
let jsonString = """
{
"zones": [
{
"name": "zoneA",
"blocks": [
{"name": "Foo"}
]
},
{
"name": "zoneB",
"blocks": [
{"street":"Broadway", "city":"New York"}
]
},
{
"name": "zoneC",
"blocks": [
{"email": "foo#bar.com"}
]
},
{
"name": "zoneD",
"blocks": [
{"phone": "555-01234"}
]
}
]
}
"""
and the corresponding element structs
struct ElementA : Decodable { let name: String }
struct ElementB : Decodable { let street, city: String }
struct ElementC : Decodable { let email: String }
struct ElementD : Decodable { let phone: String }
first decode the zones as nestedUnkeyedContainer then iterate the array and decode first the name key and depending on name the elements.
Side note: This way requires to declare the element arrays as variables.
struct Root : Decodable {
var elementsA = [ElementA]()
var elementsB = [ElementB]()
var elementsC = [ElementC]()
var elementsD = [ElementD]()
enum Zone: String, Decodable { case zoneA, zoneB, zoneC, zoneD }
private enum CodingKeys: String, CodingKey { case zones }
private enum ZoneCodingKeys: String, CodingKey { case name, blocks }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var zonesContainer = try container.nestedUnkeyedContainer(forKey: .zones)
while !zonesContainer.isAtEnd {
let item = try zonesContainer.nestedContainer(keyedBy: ZoneCodingKeys.self)
let zone = try item.decode(Zone.self, forKey: .name)
switch zone {
case .zoneA: elementsA = try item.decode([ElementA].self, forKey: .blocks)
case .zoneB: elementsB = try item.decode([ElementB].self, forKey: .blocks)
case .zoneC: elementsC = try item.decode([ElementC].self, forKey: .blocks)
case .zoneD: elementsD = try item.decode([ElementD].self, forKey: .blocks)
}
}
}
}
Decoding the stuff is straightforward
do {
let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
the "zone" property is an array of Zone objects. so you can decode them like:
enum Zone: Decodable {
case a([ElementA])
case b([ElementB])
case c([ElementC])
case d([ElementD])
enum Name: String, Codable {
case a = "zoneA"
case b = "zoneB"
case c = "zoneC"
case d = "zoneD"
}
enum RootKey: CodingKey {
case name
case blocks
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKey.self)
let zoneName = try container.decode(Name.self, forKey: .name)
switch zoneName {
case .a: try self = .a(container.decode([ElementA].self, forKey: .blocks))
case .b: try self = .b(container.decode([ElementB].self, forKey: .blocks))
case .c: try self = .c(container.decode([ElementC].self, forKey: .blocks))
case .d: try self = .d(container.decode([ElementD].self, forKey: .blocks))
}
}
}
Then you can filter out anything you like. For example you can pass in the array and get the result you asked in your question:
struct Root {
init(zones: [Zone]) {
elementsA = zones.reduce([]) {
guard case let .a(elements) = $1 else { return $0 }
return $0 + elements
}
elementsB = zones.reduce([]) {
guard case let .b(elements) = $1 else { return $0 }
return $0 + elements
}
elementsC = zones.reduce([]) {
guard case let .c(elements) = $1 else { return $0 }
return $0 + elements
}
elementsD = zones.reduce([]) {
guard case let .d(elements) = $1 else { return $0 }
return $0 + elements
}
}
let elementsA: [ElementA]
let elementsB: [ElementB]
let elementsC: [ElementC]
let elementsD: [ElementD]
}
✅ Benefits:
Retain the original structure (array of zones)
Handle repeating zones (if server sends more than just one for each zone)

Club Values of array

I am writing an iOS App in Swift 4.2.
I am geting JSON array which I convert into Swift object of Class Sector
JSON:
"Sector": [
{
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 5.6,
"CO_NAME": "REVERSE REPO"
},
{
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 1.0,
"CO_NAME": "BHARTI AIRTEL"
},
{
"REPSECTOR": "FERROUS METALS",
"PERC_HOLD": 0.3,
"CO_NAME": "COAL INDIA"
}
]
Class:
class Sector{
var REPSECTOR:String=""
var PERC_HOLD:Double=0.0
var CO_NAME:String=""
}
I am making Array array1 of type [Sector] from above.
I need to make a new array from above array which must be clubed/combined values based on REPSECTOR variable, and total of PERC_HOLD.
Expected Result:
new array with 2 elements:
1: TELECOM - SERVICES, 5.6+1.0
2: FERROUS METALS, 0.3
Suppose the array you've decoded is called array :
var array: [Sector]()
Let's create a dictionary of sectors that have the same REPSECTOR property :
let dict = Dictionary(grouping: array) {
$0.REPSECTOR
}
Now let's create an new array with sectors that have the same name, and sum their PERC_HOLD :
var newArray = [Sector]()
for (key, value) in dict {
let sector = Sector()
sector.REPSECTOR = key
sector.PERC_HOLD = value.reduce(0, { $0 + $1.PERC_HOLD })
newArray.append(sector)
}
You could check the result this way :
for s in newArray {
print(s.CO_NAME, s.PERC_HOLD)
}
First, I think you should use proper casing in your Sector type, and use Codable. For your type it would be trivial:
struct Sector: Codable {
let repsector: String
let percHold: Double
let coName: String
enum CodingKeys: String, CodingKey {
case repsector = "REPSECTOR"
case percHold = "PERC_HOLD"
case coName = "CO_NAME"
}
}
Then, assuming we have JSON like this:
let sectorsJSON = """
[
{
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 5.6,
"CO_NAME": "REVERSE REPO"
},
{
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 1.0,
"CO_NAME": "BHARTI AIRTEL"
},
{
"REPSECTOR": "FERROUS METALS",
"PERC_HOLD": 0.3,
"CO_NAME": "COAL INDIA"
}
]
""".data(using: .utf8)!
Then we can decode your JSON like this:
let decoder = JSONDecoder()
let rawSectors = try decoder.decode([Sector].self, from: sectorsJSON)
And then we can group it by .repsector and sum up percentages:
let groupedSectors = Dictionary(grouping: rawSectors, by: { $0.repsector })
.map { (arg) -> (String, Double) in
let (key, value) = arg
let percents = value.reduce(into: 0, { (acc, sector) in
acc += sector.percHold
})
return (key, percents)
}
print(groupedSectors) // [("FERROUS METALS", 0.3), ("TELECOM - SERVICES", 6.6)]
let dicSector = ["Sector": [
[
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 5.6,
"CO_NAME": "REVERSE REPO"
],
[
"REPSECTOR": "TELECOM - SERVICES",
"PERC_HOLD": 1.0,
"CO_NAME": "BHARTI AIRTEL"
],
[
"REPSECTOR": "FERROUS METALS",
"PERC_HOLD": 0.3,
"CO_NAME": "COAL INDIA"
]
]]
Codable Sector
class Sector: Codable {
var REPSECTOR:String=""
var PERC_HOLD:Double=0.0
var CO_NAME:String=""
}
Convert Dictonary to object array
var arraySector = [Sector]()
if let array = dicSector["Sector"] {
do {
let data = try JSONSerialization.data(withJSONObject: array, options: JSONSerialization.WritingOptions.prettyPrinted)
let jsonDecoder = JSONDecoder()
arraySector = try jsonDecoder.decode([Sector].self, from: data)
}
catch {
print(error.localizedDescription)
}
}
Array To Grouping
let predicate = { (element: Sector) in
return element.REPSECTOR
}
let dic = Dictionary(grouping: arraySector, by: predicate)
print(dic)
Output
["TELECOM - SERVICES": [Sector, Sector], "FERROUS
METALS": [Sector]]
This is not the best solution but working
var newArray = [Sector]()
oldArray.forEach() { element in
if newArray.contains(where: { $0.REPSECTOR == element.REPSECTOR }) {
newArray.forEach() { newElement in
if newElement.REPSECTOR == element.REPSECTOR {
newElement.CO_NAME + element.CO_NAME
} else {
newArray.append(element)
}
}
} else {
newArray.append(element)
}
}

how to add Json value into model Array to display into tableview in swift

I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong

SwiftyJSON conversion to multiple string arrays

Given the following sample JSON
{
"filters": [
{ "name" : "First Type",
"types" : ["md", "b", "pb"]},
{ "name" : "Second Type",
"types" : ["pt", "ft", "t"]},
{ "name" : "Third Type",
"types" : ["c", "r", "s", "f"]
}
],
"jobs": [
{ "title":"f",
"description" : "descrip text",
"criteria":[ "md", "ft", "s" ],
"img" : "www1"
},
{ "title":"boa",
"description" : "a description",
"criteria":[ "b", "pb", "f", "ft" ],
"img" : "www2"
},
{ "title":"BK",
"description" : "something here",
"criteria":[ "md", "pt", "ft", "b", "s" ],
"img" : "www3"
}
]
}
(Using Alamofire to create the response)
let responseJSON : JSON = JSON(response.result.value!)
1) I am trying to convert these into two String arrays. One array: let filter = [String : [String]] and another array for the jobs. How do I do it? (aka give a man a fish) The following are some sample code snippets, but none are even close to working.
let filterCategories = responseJSON["filters"].arrayValue.map({
$0["name"].stringValue
})
and
for (key,subJson):(String, JSON) in responseJSON["filters"] {
let object : filterObject = filterObject(category: key, list: subJson.arrayValue.map({ $0.stringValue }))
}
2) How do I learn how to use this properly? (aka teach a man to fish) I have been reading the documentation (https://github.com/SwiftyJSON/SwiftyJSON) but I'm struggling to understand it. I'm guessing the final answer will use .map, .stringValue, and .arrayValue. Ultimately though I'm trying to avoid lots of needless or unmanageable code.
Swift 4 provides JSON parsing support out of the box - maybe start within something like Ultimate Guide to JSON Parsing with Swift 4
Based on your available structure, I threw into a Playground and used...
// I was loading the JSON from a file within the Playground's Resource folder
// But basically, you want to end up with a reference to Data
let filePath = Bundle.main.path(forResource:"Source", ofType: "json")
let data = FileManager.default.contents(atPath: filePath!)
struct Filter: Codable {
let name: String;
let types: [String];
}
struct Job: Codable {
let title: String;
let description: String;
let criteria: [String];
let img: String;
}
struct Stuff: Codable {
let filters: [Filter];
let jobs: [Job];
}
let decoder = JSONDecoder();
let stuff = try! decoder.decode(Stuff.self, from: data!)
print("Filter:")
for filter in stuff.filters {
print(filter.name)
for type in filter.types {
print(" - \(type)")
}
}
print("Jobs:")
for job in stuff.jobs {
print(job.title)
print(job.description)
print(job.img)
for type in job.criteria {
print(" - \(type)")
}
}
to parse the results
You can implement Codable protocol to parse response. use your json response instead of this
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
i used this for playground for testing.
struct Root: Codable {
let jobs: [Jobs]
let filters: [Filters]
private enum CodingKeys: String, CodingKey {
case jobs = "jobs"
case filters = "filters"
}
}
struct Filters: Codable {
let name: String?
let typees: String?
}
struct Jobs: Codable {
let title: String?
let description: String?
let criteria: [String]?
let img: String?
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let root = try JSONDecoder().decode(Root.self, from: data as! Data)
if let name = root.jobs.first?.title {
print(name)
}
} catch let error as NSError {
print(error.description)
}

How to avoid repetitiveness with the same nested key values in JSON file

I have a rather algorithmic question. As given in the json example there are several nested keys with the same name (services). Current parseData function is simply looping through the services and it only goes as deep as the fourth nest.
I'm only interested in the name key's value within services array, but also need the id to keep track of things (e.g. when this id is pressed in table View display the corresponding title). I know this can be solved using a recursive function, but I just don't know how to start as the first for loop has slightly different structure (e.g. the first nest doesn't have an id key). Ultimately name key values will be fed into Tableview e.g. Title 1 and Title 2 will be displayed if id property is nil.
I have two questions:
1) This rather non-elegant code is not working at the moment. I have spent hours trying to fix what's wrong with it, if not days, but no success so far.
serviceName is getting printed as expected, but serviceMain.count is printing zero, that means serviceMain is empty?! I have put the code from viewDidLoad (commented out code) to didSet, otherwise serviceMain wasn't getting updated in time and tableView was empty (list of names, the code is too long now anyway, so removed the tableView bit), didSet did the trick initially, but I might have changed something and it doesn't work anymore. What is wrong with my code please?
2) Could you suggest a solution how to make this code more elegant and effective using recursive function etc? The same JSON structure could be a lot deeper (services array existing in 10 folds etc), I have only provided the following JSON as an example. Many Thanks for reading and hopefully you will be able to help!
JSON data (simplified and given as path = "http://example.com/services"
{
“Provider1”: {
"name": “Provider1”,
"services": [
{
"id": “901”,
"name": “Title 1”,
"services": [
{
"id": “950”,
"name": “Title 1A”,
"services": []
},
{
"id": “951”,
"name": “Title 1B”,
"services": []
}
]
},
{
"id": "970”,
"name": “Title 2”,
"services": [
{
"id": “971”,
"name": “Title 2A”,
"services": [ {
"id": “972”,
"name": “Subtitle 21A”,
"services": []
},
{
"id": “973”,
"name": “Subtitle 21B”,
"services": []
}]
},
{
"id": “974”,
"name": “Title 2B”,
"services": []
}
]},
“Provider2”: {...
...
}
Service.swift:
import UIKit
struct Service {
var id: Int?
var name: String?
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
init(json: [String: Any]) throws {
guard let id = (json["id"] as? NSString)?.integerValue else { throw SerializationError.missing("id is missing")}
guard let name = json["name"] as? String else { throw SerializationError.missing("name is missing")}
}
static let path = "http://example.com/services"
static func parseData(providerName: String, id: Int? = nil, completion: #escaping ([Service]) -> ()) {
var request = URLRequest(url: URL(string: path)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
var services: [Service] = []
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
// A. Main Array
guard let jsonArray = json as? [String: Any],
let providerMain = jsonArray[providerName] as? [String: Any],
let servicesMain = providerMain["services"] as? [Any] else {
return
}
// B. First nest
for service1 in servicesMain {
guard let service11 = service1 as? [String: Any],
let service12 = service11["services"] as? [Any],
let service11Id = (service11["id"] as? NSString)?.integerValue else {
return
}
if let subServices = try? Service(json: service11), id == nil {
services.append(subServices)
}
// C. Second nest
for service2 in service12 {
guard let service21 = service2 as? [String:Any],
let service22 = service21["services"] as? [Any],
let service21Id = (service21["id"] as? NSString)?.integerValue else {
return
}
if let subServices = try? Service(json: service21), service11Id == id {
services.append(subServices)
}
// D. Third nest
for service3 in service22 {
guard let service31 = service3 as? [String:Any],
let service32 = service31["services"] as? [Any],
let service31Id = (service31["id"] as? NSString)?.integerValue else {
return
}
if let subServices = try? Service(json: service31), service21Id == id {
services.append(subServices)
}
// E. Fourth nest
for service4 in service32 {
guard let service41 = service4 as? [String:Any] else {
return
}
if let subServices = try? Service(json: service41), service31Id == id {
services.append(subServices)
}
}
}
}
}
} catch {
print(error.localizedDescription)
}
completion(services)
}
}
task.resume()
}
}
ViewController.swift:
import UIKit
class ViewController: UIViewController {
var serviceMain: [String] = [] {
didSet{
Service.parseData(providerName: "Provider1", id: nil) { (services: [Service]) in
for service in services {
guard let serviceName = service.name else { return }
self.serviceMain.append(serviceName)
print(serviceName)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Below commented out code also produced zero count in serviceMain
// Service.parseData(providerName: "Provider1", id: nil) { (services: [Service]) in
// for service in services {
// guard let serviceName = service.name else { return }
// self.serviceMain.append(serviceName)
// print(serviceName)
// }
// }
print("serviceMain count: \(serviceMain.count)")
}
}
For JSON parser you can use ObjectMapper.
In your case, for example,
class ProviderWrapper: Mappable{
var provider1: Provider?
var provider2: Provider?
.....
}
class Provider: Mappable{
var name: String?
var services: [Service]?
}
class Service: Mappable{
var id: Int?
var name: String?
var services: [Service]?
}

Resources