Using this tutorial https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf I'm trying to decode a heterogeneous collection. This works fine for non optional collections however I need to extend this methods to work with optional collections as well.
I've added the methods that I think I'd need to be able to do this however I've got errors on a line that I don't know how to fix.
/// To support a new class family, create an enum that conforms to this protocol and contains the different types.
protocol ClassFamily: Decodable {
/// The discriminator key.
static var discriminator: Discriminator { get }
/// Returns the class type of the object coresponding to the value.
func getType() -> AnyObject.Type
}
/// Discriminator key enum used to retrieve discriminator fields in JSON payloads.
enum Discriminator: String, CodingKey {
case type = "type"
}
extension JSONDecoder {
/// Decode a heterogeneous list of objects.
/// - Parameters:
/// - family: The ClassFamily enum type to decode with.
/// - data: The data to decode.
/// - Returns: The list of decoded objects.
func decode<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U] {
return try self.decode([ClassWrapper<T, U>].self, from: data).compactMap { $0.object }
}
/// Decode a optional heterogeneous list of objects.
/// - Parameters:
/// - family: The ClassFamily enum type to decode with.
/// - data: The data to decode.
/// - Returns: The optional list of decoded objects.
func decodeIfPresent<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U]? {
return try self.decodeIfPresent(family: [ClassWrapper<T, U>].self, from: data)?.compactMap { $0.object }
}
private class ClassWrapper<T: ClassFamily, U: Decodable>: Decodable {
/// The family enum containing the class information.
let family: T
/// The decoded object. Can be any subclass of U.
let object: U?
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Discriminator.self)
// Decode the family with the discriminator.
family = try container.decode(T.self, forKey: T.discriminator)
// Decode the object by initialising the corresponding type.
if let type = family.getType() as? U.Type {
object = try type.init(from: decoder)
} else {
object = nil
}
}
}
}
extension KeyedDecodingContainer {
/// Decode a heterogeneous list of objects for a given family.
/// - Parameters:
/// - family: The ClassFamily enum for the type family.
/// - key: The CodingKey to look up the list in the current container.
/// - Returns: The resulting list of heterogeneousType elements.
func decode<T : Decodable, U : ClassFamily>(family: U.Type, forKey key: K) throws -> [T] {
var container = try self.nestedUnkeyedContainer(forKey: key)
var list = [T]()
var tmpContainer = container
while !container.isAtEnd {
let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self)
let family: U = try typeContainer.decode(U.self, forKey: U.discriminator)
if let type = family.getType() as? T.Type {
list.append(try tmpContainer.decode(type))
}
}
return list
}
/// Optionally decode a heterogeneous list of objects for a given family.
/// - Parameters:
/// - family: The ClassFamily enum for the type family.
/// - key: The CodingKey to look up the list in the current container.
/// - Returns: The resulting list of heterogeneousType elements.
func decodeIfPresent<T : Decodable, U : ClassFamily>(family: U.Type, forKey key: K) throws -> [T]? {
var container = try self.nestedUnkeyedContainer(forKey: key)
var list = [T]()
var tmpContainer = container
while !container.isAtEnd {
let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self)
let family: U? = try typeContainer.decodeIfPresent(U.self, forKey: U.discriminator)
if let type = family?.getType() as? T.Type {
list.append(try tmpContainer.decode(type))
}
}
if list.isEmpty {
return nil
} else {
return list
}
}
}
This line in ```JSONDecoder.decodeIfPresent()````
return try self.decodeIfPresent(family: [ClassWrapper<T, U>].self, from: data)?.compactMap { $0.object }
errors with the follow issues
Generic parameter 'U' could not be inferred
Instance method 'decodeIfPresent(family:from:)' requires that '[JSONDecoder.ClassWrapper<T, U>]' conform to 'ClassFamily'
Any pointers as to how to get these methods to work with an optional collection would be much appreciated
The issue is that you are trying to recursively call decodeIfPresent(family:from:)
from its own implementation without providing a base case. Instead, you should be calling the built-in decodeIfPresent
method, but that sadly doesn't exist on JSONDecoder
, it only exists on its containers.
You can however define your own decodeIfPresent
by simply catching DecodingError.keyNotFound
and .valueNotFound
and returning nil
for those, otherwise letting the function propagate the thrown error.
func decodeIfPresent<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U]? {
do {
return try self.decode(family: family, from: data)
} catch DecodingError.keyNotFound {
return nil
} catch DecodingError.valueNotFound {
return nil
}
}