[Pre-Pitch] Codable Customization using PropertyWrappers

This recent thread reveals that Codable, property wrappers, and optionals don't always play well together.

It looks like it's not possible to have wrappers of optional properties support missing keys, as regular properties do.

Demonstration
import Foundation

// A basic property wrapper
@propertyWrapper struct Wrapped<T> {
    var wrappedValue: T
}

// Make Wrapped support Decodable
protocol _OptionalProtocol {
    static func makeNone() -> Self
}
extension Optional: _OptionalProtocol {
    static func makeNone() -> Self { .none }
}
extension Wrapped: Decodable where T: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let type = T.self as? _OptionalProtocol.Type, container.decodeNil() {
            wrappedValue = type.makeNone() as! T
        } else {
            wrappedValue = try container.decode(T.self)
        }
    }
}

// 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: CAN'T DECODE MISSING KEY
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     // ERROR

Edit: I was wrong

1 Like