Reputation: 1391
I'm trying to decode JSON that has a variable property which conforms to a protocol.
Consider the following set of structs:
protocol P: Decodable {
var id: String { get }
}
struct A: P {
let id: String
var someThing: Double
}
struct B: P {
let id: String
var anotherThing: String
}
struct S: Decodable {
let id: String
let instanceOfProtocol: P
}
We're trying to decode S
.
The automatic synthesis of Decodable
does not work (because the decoder can't know which type P
is going to be decoded to) so I'm trying to do this in a custom initializer:
if let instance = try? container.decode(A.self, forKey: .instanceOfProtocol) {
instanceOfProtocol = instance
} else if let instance = try? container.decode(B.self, forKey: .instanceOfProtocol) {
instanceOfProtocol = instance
} else {
throw NoConformingTypeError()
}
This works, but is very verbose, repetitive, and doesn't scale well, so I'm looking for other options.
superDecoder
:let possibleTypes: [P.Type] = [A.self, B.self]
let childDecoder = try container.superDecoder(forKey: .instanceOfProtocol)
let decoded: [P] = possibleTypes.compactMap { try? $0.init(from: childDecoder) }
guard let instance = decoded.first else { throw NoConformingTypeError() }
instanceOfProtocol = instance
This works as well, but I'm not sure if superDecoder
is meant to be used this way, or if it will break in the future.
let possibleTypes: [P.Type] = [A.self, B.self]
let decoded: [P] = possibleTypes.compactMap { try? container.decode($0, forKey: .instanceOfProtocol) }
guard let instance = decoded.first else { throw NoConformingTypeError() }
instanceOfProtocol = instance
This feels like the best option so far, but doesn't compile due to Ambiguous reference to member 'decode(_:forKey:)'
.
Edit:
struct S<T: P>: Decodable {
let id: String
let instanceOfProtocol: T
}
This is really nice, because synthesis of Decodable
works again!
However, now we have to know what type T
will be, because the decoding site now requires a type:
try JSONDecoder().decode(S<A>.self, from: data)
try JSONDecoder().decode(S<B>.self, from: data)
In our use case, we can't know what the type will be before, so we'd have to check here again...
Upvotes: 2
Views: 248
Reputation: 119168
Use generic type:
struct S<T: P>: Decodable {
let id: String
let instanceOfProtocol: T
}
Remember Protocol
is not a Type
! And Swift is strongly typed language. So it MUST know the type of all objects at first place even though the actual type is not exposable to the caller of the object.
Upvotes: 2