Solution for optional List types when using Decodable
In my case I needed an optional List
because I'm decoding json into Realm objects, and it's possible that the property might not exist in the json data. The typical workaround is to manually decode and use decodeIfPresent()
. This isn't ideal because it requires boilerplate code in the model:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
// etc
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.vehicles = try container.decodeIfPresent(List<Vehicle>.self, forKey: .vehicles) ?? List<Vehicle>()
self.name = try container.decode(String.self, forKey: .name)
// etc
}
}
However, we can avoid the boilerplate by extending KeyedDecodingContainer
with an overload for List
types. This will force all lists to be decoded using decodeIfPresent()
:
Realm v5:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
//etc
}
class Vehicle: Object, Decodable {
var drivers = List<Driver>()
var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any List<> is decoded
func decode<T: Decodable>(_ type: List<T>.Type, forKey key: Key) throws -> List<T> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? List<T>()
}
}
Realm v10+:
class Driver: Object, Decodable {
@Persisted var vehicles: List<Vehicle>
@Persisted var name: String = ""
// etc
}
class Vehicle: Object, Decodable {
@Persisted var drivers: List<Driver>
@Persisted var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any @Persisted List<> is decoded
func decode<T: Decodable>(_ type: Persisted<List<T>>.Type, forKey key: Key) throws -> Persisted<List<T>> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? Persisted<List<T>>(wrappedValue: List<T>())
}
}
Edit: Clarified code examples, fixed typo