Kiran
Kiran

Reputation: 107

Swift - How to decode flat json to nested structure?

Let's say I have the following JSON which I would like to decode to this a particular structure. How do I do this

JSON:

{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr",
 "results": ["mate","bate"]
}

Decoded Struct

struct Response {
    let data: UserData,
    let results: [String]
}

The UserData struct

Struct UserData {
   let fullName: String,
   let id: Int,
   let twitter: String
}

I did my research and couldn't find a valid solution. Here is the code I wrote so far

struct UserData: Decodable {
    let fullName: String
    let id: Int
    let twitter: String
    
    enum CodingKeys: String, CodingKey {
        case fullName
        case id
        case twitter
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.fullName = try container.decode(String.self, forKey: .fullName)
        self.id = try container.decode(Int.self, forKey: .id)
        self.twitter = try container.decode(String.self, forKey: .twitter)
    }
}

struct Respone: Decodable{
    let data: UserData
    let results: [String]
}

let json = """
{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr",
 "results": ["mate","bate"]
}
""".data(using: .utf8)! // our data in native (JSON) format
let myStruct = try JSONDecoder().decode(MetaData.self, from: json) // Decoding our data
print(myStruct)

I'm getting KeynotFound error because data key is not present in the JSON. How do I solve this?

Upvotes: 1

Views: 831

Answers (2)

Sweeper
Sweeper

Reputation: 270850

You can do something like this:

struct UserData: Decodable {
    let fullName: String
    let id: Int
    let twitter: String
}

struct Response: Decodable{
    let data: UserData
    let results: [String]
    
    enum CodingKeys : CodingKey {
        case results
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        results = try container.decode([String].self, forKey: .results)
        data = try decoder.singleValueContainer().decode(UserData.self)
    }
}

The trick is to use custom decoding in Response, and after decoding the results like you do normally, get a singleValueContainer. This container will contain all the keys in your JSON, so you can use this container to decode the UserData.

Upvotes: 3

Jawad Ali
Jawad Ali

Reputation: 14397

You just need this struct.. Data is not present as a key in your response

// MARK: - User
struct User: Codable {
    let fullName: String
    let id: Int
    let twitter: String
    let results: [String]
}

Ad decode it like this

let myStruct = try JSONDecoder().decode(User.self, from: json) 

Note: you dont need init() function for codable if you are not performing anything extra.. And dont need CodingKeys if names of your struct elements are same

Here is how you can use UserData as separate object with interface segregation

struct Response: UserData, Codable {
    let fullName: String
    let id: Int
    let twitter: String
    let results: [String]
}

protocol UserData {
    var fullName: String {get}
    var id: Int {get}
    var twitter: String{get}
}

  let myStruct = try JSONDecoder().decode(Response.self, from: json)

  let userData : UserData = myStruct 

  print(userData.fullName)

Upvotes: 1

Related Questions