Response
[
{
"result": "success",
"category": [
{
"categoryID": "1",
"category_name": "Health",
"category_image": "1573456796.jpg",
"about": "Start your health journey with scientifically developed, guided meditations across below listed verticals",
"color": "",
"special": "N",
"tags": "fitness|energy|strength",
"is_active": "Y",
"description": "Explore guided health meditations to build stronger emotional, mental & physical health",
"added_on": "2019-11-22 03:21:36",
"no_items": "7"
},
{
"categoryID": "2",
"category_name": "Work",
"category_image": "1556794807.jpg",
"about": "Accelerate your work life with scientifically developed, guided meditations across below listed verticals ",
"color": "",
"special": "N",
"tags": null,
"is_active": "Y",
"description": "Explore guided work meditations to release stress, foster progressive mindset & excel at work.",
"added_on": "2019-05-02 04:30:07",
"no_items": "2"
},
{
"categoryID": "3",
"category_name": "Relationships",
"category_image": "1556794814.jpg",
"about": "Illuminate your relations with scientifically developed, guided meditations across below listed verticals",
"color": "",
"special": "N",
"tags": null,
"is_active": "Y",
"description": "Explore guided relationship meditations to create a deep & loving bond with the self and others",
"added_on": "2019-05-02 04:30:14",
"no_items": "0"
},
{
"categoryID": "4",
"category_name": "Mindfulness",
"category_image": "1556794819.jpg",
"about": "Rewire your brain & turn your NOW into WOW™ with scientifically developed, guided mindfulness meditations",
"color": "",
"special": "N",
"tags": null,
"is_active": "Y",
"description": "Explore guided mindfulness meditations to develop focus & awareness in order to turn your NOW into WOW™",
"added_on": "2019-05-02 04:30:19",
"no_items": "0"
},
{
"categoryID": "5",
"category_name": "Students",
"category_image": "1556794824.jpg",
"about": "Spark up your student life with scientifically developed, guided meditations across below listed verticals ",
"color": "",
"special": "N",
"tags": null,
"is_active": "Y",
"description": "Explore guided student meditations to strengthen mind power in order to emerge as a super student",
"added_on": "2019-05-02 04:30:24",
"no_items": "0"
},
{
"categoryID": "6",
"category_name": "Affirmations",
"category_image": "1556794832.jpg",
"about": "Prime your mind, body & emotions with scientifically developed, affirmations across below listed verticals.",
"color": "",
"special": "N",
"tags": "tags|testing|search",
"is_active": "Y",
"description": "Explore guided affirmations to train your mind & body to unleash the infinite potential within",
"added_on": "2019-05-02 04:30:32",
"no_items": "0"
}
]
}
]
Error
typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Dictionary but found an array
instead.", underlyingError: nil))
Implementation
import Foundation
import Alamofire
struct Library : Codable {
let result : String?
let category : [Category]?
enum CodingKeys: String, CodingKey {
case result = "result"
case category = "category"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
result = try values.decodeIfPresent(String.self, forKey: .result)
category = try values.decodeIfPresent([Category].self, forKey: .category)
}
}
struct Category : Codable {
let categoryID : String?
let category_name : String?
let category_image : String?
let about : String?
let color : String?
let special : String?
let tags : String?
let is_active : String?
let description : String?
let added_on : String?
let no_items : String?
enum CodingKeys: String, CodingKey {
case categoryID = "categoryID"
case category_name = "category_name"
case category_image = "category_image"
case about = "about"
case color = "color"
case special = "special"
case tags = "tags"
case is_active = "is_active"
case description = "description"
case added_on = "added_on"
case no_items = "no_items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
categoryID = try values.decodeIfPresent(String.self, forKey: .categoryID)
category_name = try values.decodeIfPresent(String.self, forKey: .category_name)
category_image = try values.decodeIfPresent(String.self, forKey: .category_image)
about = try values.decodeIfPresent(String.self, forKey: .about)
color = try values.decodeIfPresent(String.self, forKey: .color)
special = try values.decodeIfPresent(String.self, forKey: .special)
tags = try values.decodeIfPresent(String.self, forKey: .tags)
is_active = try values.decodeIfPresent(String.self, forKey: .is_active)
description = try values.decodeIfPresent(String.self, forKey: .description)
added_on = try values.decodeIfPresent(String.self, forKey: .added_on)
no_items = try values.decodeIfPresent(String.self, forKey: .no_items)
}
}
extension Library {
init(data: Data) throws {
let decoder = JSONDecoder()
self = try decoder.decode(Library.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Alamofire response handlers
extension DataRequest {
fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
print(response)
guard error == nil else { return .failure(error!) }
guard let data = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return Result { try JSONDecoder().decode(T.self, from: data) }
}
}
#discardableResult
fileprivate func responseDecodable<T: Decodable>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: decodableResponseSerializer(), completionHandler: completionHandler)
}
#discardableResult
func responsePhoto(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<Library>) -> Void) -> Self {
return responseDecodable(queue: queue, completionHandler: completionHandler)
}
}
you cannot assign the decode to self because it different type I mean self means Library but what we are decoding is array of Library so .
init(data: Data) throws {
let decoder = JSONDecoder()
self = try decoder.decode(Library.self, from: data)
}
Example I have taken response to string for easy parsing
var stringText = """
[
{
"result": "success",
"category": [
{
"categoryID": "1",
"category_name": "Health",
"category_image": "1573456796.jpg",
"about": "Start your health journey with scientifically developed, guided meditations across below listed verticals",
"color": "",
"special": "N",
"tags": "fitness|energy|strength",
"is_active": "Y",
"description": "Explore guided health meditations to build stronger emotional, mental & physical health",
"added_on": "2019-11-22 03:21:36",
"no_items": "7"
}
]
}
]
"""
let jsonData = Data(responceString.utf8)
let decoder = JSONDecoder()
do {
let responceData = try decoder.decode([Library].self, from: jsonData)
print(responceData)
} catch {
print(error.localizedDescription)
}
write
return Result { try JSONDecoder().decode([T].self, from: data) }
instead of
return Result { try JSONDecoder().decode(T.self, from: data) }
Related
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.
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.
This question already has answers here:
How to deal with completely dynamic JSON responses
(3 answers)
Closed 2 years ago.
I am dealing with this JSON using Alamofire and Codable:
[
{
"pID": "37229890-dcd8-36c4-bb63-e7b174aafeb7",
"type": "FIRST",
"content": {
"id": "ff64",
"ret": {
"name": "A",
"logoUrl": "hpng"
},
"amo": {
"value": 120.00,
"currency": "EUR"
},
"s": {
"value": 1.20,
"currency": "EUR"
},
"datetime": "",
"p": [
{
"ti": "",
"pr": {
"value": 120.00,
"currency": "EUR"
},
"pic": "string"
}
]
}
},
{
"pID": "37229890-dcd8-36c4-bb63-e7b174aafeb7",
"type": "RATE",
"content": "Rate this app"
}
]
As you can see, te value of the type "content" can be a simple String or a Struct.
I have tried a custom decoder and having a top struct but I am not able to achieve a solution for this problem.
What is the best way to solve this problem?
Is this what you are expecting?
let json = """
[
{
"type": "type1",
"content": {
"id": "ff64",
"title": "a title"
}
},
{
"type": "type2",
"content": "Rate this app"
}
]
"""
struct Type1: Decodable {
let id: String
let title: String
}
typealias Type2 = String
enum Content: Decodable {
case type1(Type1)
case type2(Type2)
enum ContentType: String, Decodable {
case type1
case type2
}
enum Keys: String, CodingKey {
case type
case content
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let type = try container.decode(ContentType.self, forKey: .type)
switch type {
case .type1:
let content = try container.decode(Type1.self, forKey: .content)
self = .type1(content)
case .type2:
let content = try container.decode(Type2.self, forKey: .content)
self = .type2(content)
}
}
}
let result = try JSONDecoder().decode([Content].self, from: json.data(using: .utf8)!)
You can apply AnyCodable custom decoder approach that can decode your needed types e.g.:
struct AnyCodable : Codable {
let value: Any
func encode(to encoder: Encoder) throws {
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let str = try? container.decode(String.self) {
value = str
} else if let content = try? container.decode(Content.self) {
value = content
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value cannot be decoded!")
}
}
}
struct Content : Codable {
let id: String
}
struct Item : Codable {
let content: AnyCodable
}
How to use:
do {
let items = try JSONDecoder().decode([Item].self, from: json.data(using: .utf8)!);
for item in items {
if let content = item.content.value as? String {
print(content)
}
else if let content = item.content.value as? Content {
print(content.id)
}
}
}
catch {
print(error)
}
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 have a json response from the fixer api. And I want to decode it to the struct which is shown below. The issue is the values of "rates" in the json is an array of key:value pairs, which has varying keys.
I have the second structure which must be mapped from these key-values.
How can i do it using Codable?
{
"success": true,
"timestamp": 1558883585,
"base": "EUR",
"date": "2019-05-26",
"rates": {
"AED": 4.116371,
"AFN": 90.160103,
"ALL": 122.341655,
"AMD": 536.359254,
"ANG": 2.097299,
"AOA": 368.543773,
"ARS": 50.418429,
"AUD": 1.618263,
"AWG": 2.012124,
"AZN": 1.910752,
"BAM": 1.955812,
"BBD": 2.227793,
"BDT": 94.245988,
"BGN": 1.956097,
"BHD": 0.421705,
"BIF": 2051.459244,
"BMD": 1.120649,
"BND": 1.539664,
"BOB": 7.729394,
"BRL": 4.508038,
"BSD": 1.118587,
"BTC": 0.00014,
"BTN": 77.755286,
"BWP": 12.040813,
"BYN": 2.323273,
"BYR": 21964.71167,
"BZD": 2.254689
}
}
If i change the 'ExchangeRate' struct as below, it is easily decoded. But I have the requirement where the "rates" should be an array of "ConvertedRate" struct.
struct ExchangeRate : Codable {
let base : String
let date : String
let rates : [String:Double]
}
This is what i need.
struct ExchangeRate : Codable {
let base : String
let date : String
let rates : [ConvertedRate] //keys are varied in json
}
struct ConvertedRate : Codable, Comparable{
let currencyName : String
let rate : Double
}
You'll have to write a custom init(from:) since you are not using the "default behavior".
More info on the Apple documentation.
Here is a possible solution:
struct ExchangeRate : Codable {
let base : String
let date : String
let rates : [ConvertedRate]
enum CodingKeys: String, CodingKey {
case base
case date
case rates
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
base = try values.decode(String.self, forKey: .base)
date = try values.decode(String.self, forKey: .date)
let additionalInfo = try values.decode([String: Double].self, forKey: .rates)
print(additionalInfo)
rates = additionalInfo.map({ ConvertedRate(name: $0.key, rate: $0.value) })
}
}
struct ConvertedRate : Codable {
let currencyName : String
let rate : Double
init(name: String, rate: Double) {
self.currencyName = name
self.rate = rate
}
}
Side Note: The current code can decode your JSON as you want, but it doesn't re-encoded as it was, because it's replicating the Swift structure:
{
"base": "EUR",
"date": "2019-05-26",
"rates": [{
"currencyName": "BSD",
"rate": 1.118587
}, {
"currencyName": "BWP",
"rate": 12.040813
}, {
"currencyName": "BYN",
"rate": 2.3232729999999999
}, {
"currencyName": "BBD",
"rate": 2.2277930000000001
}, {
"currencyName": "BOB",
"rate": 7.7293940000000001
}, {
"currencyName": "BAM",
"rate": 1.9558120000000001
}, {
"currencyName": "AUD",
"rate": 1.618263
}, {
"currencyName": "AFN",
"rate": 90.160103000000007
}, {
"currencyName": "BYR",
"rate": 21964.711670000001
}, {
"currencyName": "BRL",
"rate": 4.508038
}, {
"currencyName": "BMD",
"rate": 1.120649
}, {
"currencyName": "BGN",
"rate": 1.956097
}, {
"currencyName": "BHD",
"rate": 0.421705
}, {
"currencyName": "ANG",
"rate": 2.097299
}, {
"currencyName": "AOA",
"rate": 368.54377299999999
}, {
"currencyName": "BZD",
"rate": 2.2546889999999999
}, {
"currencyName": "ARS",
"rate": 50.418429000000003
}, {
"currencyName": "BTC",
"rate": 0.00013999999999999999
}, {
"currencyName": "BIF",
"rate": 2051.4592440000001
}, {
"currencyName": "AWG",
"rate": 2.012124
}, {
"currencyName": "AED",
"rate": 4.116371
}, {
"currencyName": "AMD",
"rate": 536.35925399999996
}, {
"currencyName": "BDT",
"rate": 94.245987999999997
}, {
"currencyName": "BND",
"rate": 1.5396639999999999
}, {
"currencyName": "BTN",
"rate": 77.755285999999998
}, {
"currencyName": "AZN",
"rate": 1.910752
}, {
"currencyName": "ALL",
"rate": 122.341655
}]
}
You have to write a custom initializer which maps the dictionary key-value pairs to an array of the custom struct.
It's not necessary to adopt Codable in CurrencyRate because the instances are created manually
let jsonString = """
{
"success": true,
"timestamp": 1558883585,
"base": "EUR",
"date": "2019-05-26",
"rates": {
"AED": 4.116371,"AFN": 90.160103,"ALL": 122.341655,"AMD": 536.359254,"ANG": 2.097299,"AOA": 368.543773,"ARS": 50.418429,"AUD": 1.618263,"AWG": 2.012124,"AZN": 1.910752,"BAM": 1.955812,"BBD": 2.227793,"BDT": 94.245988,"BGN": 1.956097,"BHD": 0.421705,"BIF": 2051.459244,"BMD": 1.120649,"BND": 1.539664,"BOB": 7.729394,"BRL": 4.508038,"BSD": 1.118587,"BTC": 0.00014,"BTN": 77.755286,"BWP": 12.040813,"BYN": 2.323273,"BYR": 21964.71167,"BZD": 2.254689
}
}
"""
struct ExchangeData : Codable {
let timestamp : Date
let base : String
let date : String
let rates : [CurrencyRate]
private enum CodingKeys: String, CodingKey { case timestamp, base, date, rates}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
timestamp = try container.decode(Date.self, forKey: .timestamp)
base = try container.decode(String.self, forKey: .base)
date = try container.decode(String.self, forKey: .date)
let rateData = try container.decode([String:Double].self, forKey: .rates)
rates = rateData.map(CurrencyRate.init)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(timestamp, forKey: .timestamp)
try container.encode(base, forKey: .base)
try container.encode(date, forKey: .date)
var rateData = [String:Double]()
rates.forEach{ rateData[$0.name] = $0.rate }
try container.encode(rateData, forKey: .rates)
}
}
struct CurrencyRate {
let name : String
let rate : Double
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(ExchangeData.self, from: data)
print(result)
} catch {
print(error)
}