Search code examples
iosswiftdecoding

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?


Solution

  • 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.