Search code examples
jsonswift4decodable

Swift 4 decodable nested json with random key attributes


I'm having problems decoding json. I've followed lots of tutorials but non use complex json structures. For simplicity I minimized the code and use Dog as example.

In following json i'm mostly only interested in the Dog structs. The json "Data" attribute contains random dog names. So I cannot use coding keys because I dont know the attribute name.

{
     "Response": "success"
     "BaseLinkUrl": "https://wwww.example.com",
     "Data": {
         "Max": {
             "name": "Max",
             "breed": "Labrador"
         },
         "Rocky": {
             "name": "Rocky",
             "breed": "Labrador"
         },
         ...
    }
}

I have following structs:

struct DogResponse : Decodable {
    let data : DogResponseData

    enum CodingKeys: String, CodingKey {
        case data = "Data"
    }
}

struct DogResponseData: Decodable {
    let dog: Dog //this is a random variable name

    enum CodingKeys: String, CodingKey {
        case dog = "??random_variable_dog_name??"
    }
}

struct Dog: Decodable {
    let name: String
    let type: String

    enum CodingKeys: String, CodingKey {
        case name
        case type = "breed"
    }
}

collecting the Dog structs:

let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse)

What do I need to do in my "DogResponseData" struct for swift to recognize a random variable that contains my Dog struct?


Solution

  • A possible solution is to write a custom initializer to decode the dictionaries as [String:Dog] and map the values to an array

    struct Dog : Decodable {
        let name : String
        let breed : String
    }
    
    struct DogResponse : Decodable {
        let dogs : [Dog]
    
        private enum CodingKeys: String, CodingKey {
            case data = "Data"
        }
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            let data = try values.decode([String : Dog].self, forKey: .data)
            dogs = Array(data.values)
        }
    }
    

    let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
    print(dogResponse.dogs)
    

    ===========================================================================

    Or if you want to keep the dictionary structure it's still shorter

    struct Dog : Decodable {
        let name : String
        let breed : String
    }
    
    struct DogResponse : Decodable {
        let dogs : [String : Dog]
    
        private enum CodingKeys: String, CodingKey {
            case dogs = "Data"
        }
    }