1

The API send me this json :

{
"name": "John Doe",
"details": [{
    "name": "exampleString"
}, {
    "name": [1, 2, 3]
}]

}

The problem here is that the details array have two dictionary of different value type. how decode this json in model using the decodable protocol of swift4 ?

2
  • Override init(from: Decoder) and implement your own logic there. Commented Nov 25, 2017 at 18:07
  • the problem is that the decode function take the class type in its parameters for exemple .decode([String:String].self) but in my example the type is not the same (String and Array) so i tried to put .decode([String:Any].self) but this code don't compile because Any donc implement the Codable protocol Commented Nov 25, 2017 at 18:20

1 Answer 1

1

I don't recommend that you structure your JSOn with heterogenous types; in theis case details.name can be either a string or an array of Int. While you can do this is Swift, its kind of messy since its a statically typed language by default. In the event you can't change your JSON to something cleaner here a playground showing is how you opt into dynamic behavior with Any.

//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit

let json = """
{
"name": "John Doe",
"details": [{
"name": "exampleString"
}, {
"name": [1, 2, 3]
}]
}
"""


struct Detail {
    var name: Any?
    var nameString: String? {
        return name as? String
    }
    var nameIntArray: [Int]? {
        return name as? [Int]
    }
    enum CodingKeys: CodingKey {
        case name
    }
}

extension Detail: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if let string = name as? String {
            try container.encode(string, forKey: .name)
        }
        if let array = name as? [Int] {
            try container.encode(array, forKey: .name)
        }
    }
}


extension Detail: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let string = try? values.decode(String.self, forKey: .name) {
            name = string
        } else if let array = try? values.decode(Array<Int>.self, forKey: .name) {
            name = array
        }
    }
}

struct Record: Codable {
    var name: String
    var details: [Detail]
}

let jsonDecoder = JSONDecoder()
let record = try! jsonDecoder.decode(Record.self, from: json.data(using: .utf8)!)
print("\(record.details.first!.name!) is of type: \(type(of:record.details.first!.name!))")
print("\(record.details.last!.name!) is of type: \(type(of:record.details.last!.name!))")

the output is:

exampleString is of type: String
[1, 2, 3] is of type: Array<Int>
Sign up to request clarification or add additional context in comments.

3 Comments

Yes, I think that's the cleanest way. Basically the same solution as stackoverflow.com/a/47215561/341994 Note that the OP probably has no choice about the API, so there is no point saying you don't recommend this structure. :)
Actually I was going to suggest not storing it as an Any at all and giving it strongly typed Optional names (which I have as computed properties in the example) or storing it in an enum with associated value; I think both ways are more swifty than using Any, which is lind of sloppy.
Sure. The key point, though, is the struct that unites string and array-of-int as a single type. If you have no way of knowing which is coming, I think that's the only reasonable way.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.