I would like to pass the parent area Id to it's children areas while parsing the nested JSON structure as per the attached response, Here I would like to insert 'parentId' for each children which will link to it's immediate parent area,
{
"areas": [
{
"id": "271341877549072423",
"name": "Breeze Office Tower",
"children": [
{
"id": "271341877549072424",
"name": "100 flinders street",
"position": 0,
"children": []
},
{
"id": "271341877549130929",
"name": "100 flinders street",
"position": 1,
"children": []
},
{
"id": "271341877549072425",
"name": "100 Flinder Stree",
"position": 2,
"children": [
{
"id": "271341877549072426",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549072427",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
},
{
"id": "271341877549130931",
"name": "100 Flinder Stree",
"position": 3,
"children": [
{
"id": "271341877549130933",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549130935",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
}
]
}
]
}
My JSON Codable model struct looks like,
struct AreaModel: Decodable {
var areas: [NestedAreaModel]?
}
struct NestedAreaModel: Codable {
let areaId: String
let areaName: String
let children: [NestedAreaModel]
let hasChildren: Bool
var areaPosition: Int16?
var parentId: String?
var projectId: String?
enum CodingKeys: String, CodingKey {
case areaId = "id"
case areaName = "name"
case areaPosition = "position"
case children
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
self.children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
if !self.children.isEmpty {
self.hasChildren = true
self.parentId = self.areaId
} else {
self.hasChildren = false
}
}
}
Here I am not able to set the parent Id, its pointing its own id always.
As I allready pointed out in the comments you would need to iterate over the decoded children and set their respective parentId to the current areaId.
One possible Solution would be:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
// decode the children to a local var
var children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
// if there are children loop over them and assign your id
if !children.isEmpty {
self.hasChildren = true
for (index, _ ) in children.enumerated(){
children[index].parentId = areaId
}
} else {
self.hasChildren = false
}
// assign to self
self.children = children
}
Tested with the given JSON:
let decoded = try JSONDecoder().decode(AreaModel.self, from: data)
print(decoded.areas?[0].areaId)
print(decoded.areas?[0].children[0].parentId)
Result:
Optional("271341877549072423")
Optional("271341877549072423")
Remarks:
Regarding your comment on complexity:
children[index].parentId = areaId runs exactly once per child no matter of the level deapth. So this function is (O)n.
Related
I've created NSObject class for "CurrentUser",
here, sign-up API will call & response data will insert into the "CurrentUser" model.
This is one type of global model, like in any screen user_logic_details will fetch, modify & save.
Example :
let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "abc#gmail.com"
CurrentUser.saveLoginData(loginData: loginuser)
// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "abc#gmail.com"
in the above example if I write the below code then it shows nil data
let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "abc#gmail.com"
CurrentUser.saveLoginData(loginData: loginuser)
// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "abc#gmail.com"
// not working
let roleName = CurrentUser.getLoginData()?.roles?.name ?? "" // showing get ""
I'm not able to find exact issues here,
Check below the code of how I use the model class, saving data into UserDefaults & retrieve data from the model.
class CurrentUser: NSObject, NSCoding, NSKeyedUnarchiverDelegate {
var email : String!
var roles : UserRole!
private var _isLoggedIn = false
required convenience init(coder aDecoder: NSCoder) {
self.init()
roles = aDecoder.decodeObject(forKey: "roles") as? UserRole
email = aDecoder.decodeObject(forKey: "email") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
aCoder.encode(roles, forKey: "roles")
}
class func getLoginData() -> CurrentUser? {
let userDefaults = UserDefaults.standard
if let UserData = userDefaults.object(forKey: "loginUser") {
guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(UserData as! Data)
else {
return nil
}
return unarchivedFavorites as? CurrentUser
} else {
return CurrentUser()
}
}
class func saveLoginData(loginData: CurrentUser?) {
do {
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: loginData as Any, requiringSecureCoding: false)
let userDefaults: UserDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: "loginUser")
userDefaults.synchronize()
} catch {
print("Couldn't write file")
}
}
func unarchiver(_ unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
print("classNames", classNames)
return nil
}
}
class UserRole: NSObject, NSCoding {
var name : String!
required convenience init(coder aDecoder: NSCoder) {
self.init()
name = aDecoder.decodeObject(forKey: "name") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
}
}
The above model is for reference purposes.
I need to convert the below Response to the whole model class
{
"addressList": [
{
"addressLabel": "string",
"city": "string",
"country": "string",
"createdAt": "2022-03-16T12:10:41.148Z",
"homePhone": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDefault": true,
"isDeleted": true,
"state": "string",
"streetAddress": "string",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0,
"zip": "string"
}
],
"ageRange": "string",
"badges": [
{
"badge": {
"createdAt": "2022-03-16T12:10:41.148Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"image": "string",
"isDeleted": true,
"label": "string",
"name": "string",
"status": "ACTIVE",
"updatedAt": "2022-03-16T12:10:41.148Z",
"users": [
null
],
"version": 0
},
"userBadgeId": {
"badgeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
],
"classesTaken": 0,
"createdAt": "2022-03-16T12:10:41.148Z",
"deviceList": [
{
"createdAt": "2022-03-16T12:10:41.148Z",
"deviceId": "string",
"deviceName": "string",
"deviceType": "ANDROID",
"display": "string",
"fcmToken": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"osVersion": "string",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0
}
],
"displayName": "string",
"displayPic": "string",
"dob": "2022-03-16T12:10:41.148Z",
"email": "string",
"experience": 0,
"firebaseToken": "string",
"firebaseUserId": "string",
"followerCount0l": 0,
"fullName": "string",
"gender": "FEMALE",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"lastLoginTime": "2022-03-16T12:10:41.148Z",
"location": "string",
"phoneNumber": "string",
"roles": [
{
"createdAt": "2022-03-16T12:10:41.148Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"name": "ROLE_ADMIN",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0
}
],
"rubyBalance": 0,
"signupDateTime": "2022-03-16T12:10:41.148Z",
"signupSource": {
"buildNumber": "string",
"deviceId": "string",
"deviceType": "ANDROID",
"osVersion": "string"
},
"status": "ACTIVE",
"studentsTaught": 0,
"updatedAt": "2022-03-16T12:10:41.148Z",
"userInterest": [
"string"
],
"userName": "string",
"version": 0
}
If anyone has a better way to do it or a better solution, please answer it.
Your code reads like Objective-C from a few years ago. Something more modern and Swift-like might be:
#propertyWrapper
struct UserDefaultsCodableWrapper<T: Codable> {
let key: String
let defaultValue: T?
init(_ key: String, defaultValue: T?) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T? {
get {
guard let data = UserDefaults.standard.data(forKey: key),
let decoded = try? JSONDecoder().decode(T.self, from: data)
else { return defaultValue }
return decoded
}
set {
let encoded = try? JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: key)
}
}
}
struct User: Codable {
var email: String // TODO make a codable "Email" struct type that validates its input, no invalid strings allowed
var roles: [UserRole]
}
struct UserRole: Codable {
var name: String
}
class AppState {
#UserDefaultsCodableWrapper("CurrentUser", defaultValue: nil)
var currentUser: User?
}
class ViewController: UIViewController {
let state = AppState()
override func viewDidLoad() {
super.viewDidLoad()
print(String(describing: state.currentUser)) // nil
state.currentUser = User(email: "foo#bar.baz", roles: [.init(name: "Foo"), .init(name: "Bar")])
print(String(describing: state.currentUser)) // Optional(User...
Also worth pointing you to app.quicktype.io which can generate you codable models from your json.
I am currently building a food app using Yelp Fusion API. There are two models called Business and BusinessDatail in my project. To list up restaurants in a tableView, I fetch a restaurant list from backend and custom decode it as an array of Business, [Business]. And then, when a tableViewCell of Business is clicked, I fetch detailed business info and custom decode it as a BusinessDetail.
struct Business {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
}
struct BusinessDetail {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
Both Business and BusinessDetail are custom decoded models and they are currently initialised this way.
extension Business: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decodeIfPresent(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
}
}
extension PlaceDetail: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
case isClaimed = "is_claimed"
case isClosed = "is_closed"
case url
case phone
case photoURLs = "photos"
case hours
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decode(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
isClaimed = try container.decode(Bool.self, forKey: .isClaimed)
isClosed = try container.decode(Bool.self, forKey: .isClosed)
url = try container.decode(URL.self, forKey: .url)
phone = try container.decode(String.self, forKey: .phone)
photoURLs = try container.decode([URL].self, forKey: .photoURLs)
hours = try container.decodeIfPresent([Hours].self, forKey: .hours)
}
}
Here is the json for a business list.
{
"businesses": [
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"transactions": [],
"price": "$",
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "",
"display_phone": "",
"distance": 383.2254392716822
},
{
"id": "KaIoCOg-IZJtLdN39Cw__Q",
"alias": "strauss-brisbane",
"name": "Strauss",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/eYKx68uOaEY5k9Jt4TrQPw/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/strauss-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 33,
"categories": [
{
"alias": "cafes",
"title": "Cafes"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.469804,
"longitude": 153.027384
},
"transactions": [],
"price": "$",
"location": {
"address1": "189 Elizabeth St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"189 Elizabeth St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "+61732365232",
"display_phone": "+61 7 3236 5232",
"distance": 247.3760157828213
}
}
And this is json response for individual BusinessDetail.
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_claimed": false,
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"phone": "",
"display_phone": "",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
],
"cross_streets": ""
},
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"photos": [
"https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"https://s3-media1.fl.yelpcdn.com/bphoto/L8dJYv1GCW1m5IvD0M3qXw/o.jpg",
"https://s3-media4.fl.yelpcdn.com/bphoto/oUH4cJmPRuAs_jv_xRtXQg/o.jpg"
],
"price": "$",
"hours": [
{
"open": [
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 0
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 1
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 1
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 2
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 2
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 3
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 3
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 5
}
],
"hours_type": "REGULAR",
"is_open_now": true
}
]
}
As you can see, BusinessDetail has exactly same info as Business at the top and I'd like to change BusinessDetail like below.
struct BusinessDetail {
let business: Business
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
However, I am not sure if it is technically possible.
Here's a conceptual example, but you can apply the same principle to your case, with the following JSON:
{ "a": 1, "b": 2, "c": "three" }
and these models:
struct Smaller: Decodable {
var a, b: Int
}
struct Larger: Decodable {
var smaller: Smaller
var c: String
}
Decoding Smaller doesn't need any extra work (i.e. no need to manually implement the init(from: Decoder) initializer):
let decoder = JSONDecoder()
let small = try! decoder.decode(Smaller.self, from: jsonData)
But with Larger, you need to manually do this:
struct Larger: Decodable {
var smaller: Smaller
var c: String
enum CodingKeys: CodingKey {
case c
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.smaller = try Smaller.init(from: decoder)
self.c = try container.decode(String.self, forKey: .c)
}
}
I want to filter all details(school, city, name, active) whose active value is true. I have stored value of key "details"
let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any>
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
Expected Result
[
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true }
]
}
]
You can use filter
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = students.filter { (item) -> Bool in
guard let active = item["Active"] as? Bool else {return false}
return active
}
print(activeStudents)
}
or you can use shourthand
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = (details["students"] as?
[[String:Any]])?.filter{ $0["Active"] as? Bool == true}
print(activeStudents)
}
You start realising the true elegance of Swift once you start turning this into regular objects using Codable. This will let you do things as in the Playground:
import Cocoa
let jsonData = """
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
""".data(using: .utf8)!
struct Student : Codable {
let name: String
let active: Bool
enum CodingKeys: String, CodingKey {
case name
case active = "Active"
}
}
struct School : Codable {
let code: Int
let school: String
let students: [Student]
}
struct Details: Codable {
let details: School
}
do {
let det = try JSONDecoder().decode(Details.self, from: jsonData)
print(det)
let activeStudents = det.details.students.filter({(student)->Bool in student.active})
print(activeStudents)
} catch {
print(error)
}
This is obviously much easier to understand and Xcode can also support you much better during the process. The effort spent on the parser is minimal and easily recovered by the sheer elegance and clarity of the final filtering line.
Could anyone tell me what I am doing wrong?
Here is JSON I'm trying to parse:
{
"results": {
"AF": {
"alpha3": "AFG",
"currencyId": "AFN",
"currencyName": "Afghan afghani",
"currencySymbol": "؋",
"id": "AF",
"name": "Afghanistan"
},
"AI": {
"alpha3": "AIA",
"currencyId": "XCD",
"currencyName": "East Caribbean dollar",
"currencySymbol": "$",
"id": "AI",
"name": "Anguilla"
}
}
}
My code :
class Results: Codable {
let results: [Country]
init(results: [Country]) {
self.results = results
}
}
class Country: Codable {
let currencyId: String
let currencyName: String
let currencySymbol: String
let id: String
let name: String
init(currencyId :String, currencyName: String, currencySymbol: String, id: String, name: String ) {
self.currencyId = currencyId
self.currencyName = currencyName
self.currencySymbol = currencySymbol
self.id = id
self.name = name
}
}
I have looked at Apple's Documentation on decoding nested structs, but I still do not understand how to do the different levels of the JSON properly.
Thanks.
Check the outlined value for the key "results".
"results": {
...
}
{...} represents JSON object. A Swift struct (or class if you think it's better) would be appropriate for JSON object in some cases.
In other cases, Swift Dictionary may be more appropriate.
And each value of this JSON object takes this form:
{
"alpha3": ...,
"currencyId": ...,
"currencyName": ...,
"currencySymbol": ...,
"id": ...,
"name": ...
}
which matches your Country.
So, you just need to change the type of results in your Results class.
class Results: Codable {
let results: [String: Country]
init(results: [String: Country]) {
self.results = results
}
}
Having the same name (without case) for a property and its class might cause some confusion in the future, but I keep it as is as for now.
You can test it like this:
(Assuming to be tested in the Playground with modified Results and your Country.)
let jsonText = """
{
"results": {
"AF": {
"alpha3": "AFG",
"currencyId": "AFN",
"currencyName": "Afghan afghani",
"currencySymbol": "؋",
"id": "AF",
"name": "Afghanistan"
},
"AI": {
"alpha3": "AIA",
"currencyId": "XCD",
"currencyName": "East Caribbean dollar",
"currencySymbol": "$",
"id": "AI",
"name": "Anguilla"
}
}
}
"""
let jsonData = jsonText.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let results = try decoder.decode(Results.self, from: jsonData)
print(results) //-> __lldb_expr_1.Results
} catch {
print(error)
}
This is my json to parse (example):
[
{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
},
{
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}
]
paste it this json viewer to view it easily.
as you can see, it is an array (of teams).
I need to get each team and do something with it.
After many attempts, I tried using SwiftyJSON, because I thought it will be easier. But, it did not worked for me.
This is what I tried:
let array = JSON(response)
// print each subJSON in array
for team in array.arrayValue {
print(team)
}
But the loop does not work. It does not go in to the loop at all.
Maybe it does not understand that my json is an array.
I can see the array object in the debugger. It looks like this:
How can I get these sub-JSONs?
Thanks.
I think you should use
let array = JSON(parseJSON: response)
instead of
let array = JSON(response)
SwiftyJSON contains methods to parse JSON string into a JSON object, check documentation
/**
Parses the JSON string into a JSON object
- parameter json: the JSON string
- returns: the created JSON object
*/
public init(parseJSON jsonString: String) {
if let data = jsonString.data(using: .utf8) {
self.init(data)
} else {
self.init(NSNull())
}
}
/**
Creates a JSON from JSON string
- parameter string: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
#available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`")
public static func parse(json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap{ JSON(data: $0) } ?? JSON(NSNull())
}
or alternatively you can convert son string into son object like
Swift 3:
let dataFromString = response.data(using: .utf8)
let jsonArray = JSON(data: dataFromString!)
In the following example, I save team names in an array. I've tested it.
var names = [String]()
if let array = json.array {
for i in 0..<array.count {
let name = array[i]["name"]
names.append(name.stringValue)
}
}
print(names) // ["Team name", "Team name"]
Here is the answer for Swift 5. In My case data response is something like below :
[
{
"Name": "Some Type",
"Data": [
{
"ParentId": 111,
"Code": "Personal",
"SortOrder": 1,
"Name": "Personal",
"Id": 323
},
{
"ParentId": null,
"Code": "Work",
"SortOrder": 2,
"Name": "Work",
"Id": 324
}
],
"KeyType": "Integer"
},
{
"Name": "Phone Type",
"Data": [
{
"ParentId": null,
"Code": "Phone",
"SortOrder": 1,
"Name": "Phone",
"Id": 785
},
{
"ParentId": null,
"Code": "Cell",
"SortOrder": 2,
"Name": "Cell",
"Id": 786
},
{
"ParentId": null,
"Code": "Fax",
"SortOrder": 3,
"Name": "Fax",
"Id": 787
},
{
"ParentId": null,
"Code": "Home",
"SortOrder": 4,
"Name": "Home",
"Id": 788
},
{
"ParentId": null,
"Code": "Office",
"SortOrder": 5,
"Name": "Office",
"Id": 789
}
],
"KeyType": "Integer"
}
]
I was handled it with following code.
struct responseObjectClass:BaseModel {
var responsearray: [arrayData]? = nil
init(json: JSON) {
responsearray = json.arrayValue.map { arrayData(json: $0) }
}
}
struct arrayData:BaseModel {
let Name: String?
var DataValue: [DataLookup]? = nil
let KeyType: String?
init(json: JSON) {
Name = json["Name"].stringValue
DataValue = json["Data"].arrayValue.map { DataLookup(json: $0) }
KeyType = json["KeyType"].stringValue
}
}
struct DataLookup:BaseModel {
let ParentId: Any?
let Code: String?
let SortOrder: Int?
let Name: String?
let Id: Int?
init(json: JSON) {
ParentId = json["ParentId"]
Code = json["Code"].stringValue
SortOrder = json["SortOrder"].intValue
Name = json["Name"].stringValue
Id = json["Id"].intValue
}
}
BaseModel is Optional it's just used for init Json.
protocol BaseModel {
init(json: JSON)
}
Without SwiftyJSON
Below is the valid JSON
data.json File
[{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
}, {
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}]
Below is the code to read your json.
if let path = Bundle.main.path(forResource: "data", ofType: "json") {
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSArray {
for (_, item) in jsonArray.enumerated() {
let itemDict = item as! NSDictionary
let id = itemDict["id"] as! Int
let name = itemDict["name"] as! String
let shower = itemDict["shower"] as! NSDictionary
let showerId = shower["id"] as! Int
let showerStatus = shower["status"] as! Int
let startLocation = shower["startLocation"] as! NSDictionary
let startLocationId = startLocation["id"] as! Int
let startLocationName = startLocation["name"] as! String
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}
this is what worked for me:
// Convert JSON to Array
func JSONToArray(_ json: String) -> Array<Any>? {
if let data = json.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? Array
} catch let error as NSError {
print(error)
}
}
return nil
}
After using this function i could loop trough the sub JSONs.
Thanks.