Paulo Andrade

Keeper of Secrets.

twitter github stackoverflow linkedin email
AnyDecodable
Feb 12, 2020
2 minutes read

Swift’s Codable is great, but sometimes the type-safety can get in the way. There are cases when decoding JSON where you don’t know beforehand what is it that you are decoding.

In my case, the JSON had a type field that disambiguated what it represents. Intuitively, I felt like I needed to peek into the JSON to figure out what the type is and then call decoder.decode(Foo.self, jsonData). After playing around with this a bit, I figured that instead of “peeking” into the JSON I might just as well try and decode whatever types I was expecting. And so AnyDecodable was born:

The cool thing about it is that it uses the Decododer’s userInfo dictionary to pass the list of types we’re expecting. Having this flexibility at the call site is great. Here’s an example of it in action:

struct Person: Codable {
    let name: String
}

struct Dog: Codable {
    let breed: String
}

let decoder = JSONDecoder()
decoder.userInfo[AnyDecodable.supportedTypes] = [Person.self, Dog.self]
let p = try decoder.decode(AnyDecodable.self, from: "{\"name\":\"Joe\"}".data(using: .utf8)!).value
print(p) // Person(name: "Joe")

let d = try decoder.decode(AnyDecodable.self, from: "{\"breed\":\"Caniche\"}".data(using: .utf8)!).value
print(d) // Dog(breed: "Caniche")

And AnyCodable itself isn’t that complicated:

struct AnyDecodable: Decodable {
    static let supportedTypes = CodingUserInfoKey(rawValue: "SupportedTypes")!
    
    enum Errors: Error {
        case missingSupportedTypesList
        case unknownType
    }
    
    typealias DecodableValue = Any & Decodable
    let value: DecodableValue
    
    init(from decoder: Decoder) throws {
        guard let supportedTypes = decoder.userInfo[AnyDecodable.supportedTypes] as? [Any.Type] else {
            throw Errors.missingSupportedTypesList
        }
        
        let decodableTypes = supportedTypes.compactMap { $0 as? Decodable.Type }

        var decodedValue: DecodableValue?
        for decodableValueType in decodableTypes {
            do {
                decodedValue = try decodableValueType.init(from: decoder)
                break
            } catch {
                continue
            }
        }
        guard let v = decodedValue else {
            throw Errors.unknownType
        }
        value = v
    }
}

Here’s a gist you can just copy paste into a playground.



Back to posts