Search code examples
iosjsonswiftcodableany

JSON Codable - What to do with [Any]?


I have a JSON with the following structure :

         "properties":[

            {

               "name":"Quality",
               "values":[],
               "displayMode":0,
               "type":6
            },
            {

               "name":"Armour",
               "values":[],
               "displayMode":0,
               "type":16
            },
            {

               "name":"Evasion Rating",
               "values":[],
               "displayMode":0,
               "type":17
            }
         ]

The API is always returning an array for "value" with first element being a String, and second element being an Int.

    "values":[

                  [
                     "+26%",
                     1
                  ]

               ],

Here’s how I mapped the JSON so far :

struct Properties: Codable {
    var name: String
    var values: [Any]
    var displayMode: Int
    var type: Int
}

At which point Xcode complains because Type 'Properties' does not conform to protocol 'Decodable'

So, I know that Any doesn't conform to codable but the thing is I don't how to convert this [Any] into something Swift can work with...

Can someone share a hint to the solution ?

Thank you very much :)


Solution

  • You need to use an unkeyed container for this when decoding in your custom init(from:).

    If you know there is always one string followed by one integer you can define the struct for the values like this

    struct Value: Decodable {
        let string: String?
        let integer: Int?
    
        init(from decoder: Decoder) throws {
            var container = try decoder.unkeyedContainer()
            string = try container.decodeIfPresent(String.self)
            integer = try container.decodeIfPresent(Int.self)
        }
     }
    

    If there could be more elements you can use a loop instead

    struct Value: Decodable {
        let strings: [String]
        let integers: [Int]
    
        init(from decoder: Decoder) throws {
            var container = try decoder.unkeyedContainer()
            var strings = [String]()
            var integers = [Int]()
    
            while !container.isAtEnd {
                do {
                    let string = try container.decode(String.self)
                    strings.append(string)
                } catch {
                    let integer = try container.decode(Int.self)
                    integers.append(integer)
                }
            }
            self.integers = integers
            self.strings = strings
        }
    }