TIL that code synthesized for Codable is as malleable as other pieces of code. Thanks!
I've rewritten my sample code above, with full support for missing values:
It works!
import Foundation
// A basic property wrapper
@propertyWrapper struct Wrapped<T> {
var wrappedValue: T
}
// Make Wrapped support Decodable
extension Wrapped: Decodable where T: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self)
}
}
// Make Wrapped support decoding nil and missing values
protocol OmittableType {
static var nilValue: Self { get }
}
extension Wrapped: OmittableType where T: ExpressibleByNilLiteral {
static var nilValue: Self { .init(wrappedValue: nil)}
}
extension KeyedDecodingContainer {
func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T where T : Decodable, T: OmittableType {
return try decodeIfPresent(T.self, forKey: key) ?? T.nilValue
}
}
// Setup
let decoder = JSONDecoder()
let jsonWithValue = #"{"property":"Hello"}"#.data(using: .utf8)!
let jsonWithNull = #"{"property":null}"#.data(using: .utf8)!
let jsonEmpty = #"{}"#.data(using: .utf8)!
// A regular decodable struct: full success
struct Struct: Decodable {
var property: String?
}
try! decoder.decode(Struct.self, from: jsonWithValue).property // "Hello"
try! decoder.decode(Struct.self, from: jsonWithNull).property // nil
try! decoder.decode(Struct.self, from: jsonEmpty).property // nil
// A decodable struct with wrapped property: full success
struct WrappedStruct: Decodable {
@Wrapped var property: String?
}
try! decoder.decode(WrappedStruct.self, from: jsonWithValue).property // "Hello"
try! decoder.decode(WrappedStruct.self, from: jsonWithNull).property // nil
try! decoder.decode(WrappedStruct.self, from: jsonEmpty).property // nil