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.