# PropertyWrapper, Optional, and Codable

**URL:** <https://forums.swift.org/t/propertywrapper-optional-and-codable/60681>\
**Category:** Using Swift\
**Tags:** codable, propertywrapper\
**Created:** [October 6, 2022, 2:57am UTC](https://forums.swift.org/t/propertywrapper-optional-and-codable/60681 "2022-10-06T02:57:36Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![rayx](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/rayx/32/12204_2.png) [@rayx](https://forums.swift.org/u/rayx)\
**Post date:** [October 6, 2022, 2:57am UTC](https://forums.swift.org/t/propertywrapper-optional-and-codable/60681/1 "2022-10-06T02:57:36Z")

</div>

I'm using property wrapper to serialzie `Decimal` into `String`. My code works fine for types like `Decimal` and `Dictionary` containg Decimal, but fails with `Decimal?`. I have read related discussion [here](https://forums.swift.org/t/using-property-wrappers-with-codable/29804/12) and [here](https://betterprogramming.pub/create-a-swift-property-wrapper-that-works-with-optional-and-non-optional-values-fc09d27a44f3). I have a workaround, but I suspect the decoding failure is a bug in codable sythesizer and wonder if there is a way to avoid the issue in the first place.

Below is the code to demonstrate the issue (it's a bit long, but should be simple to follow).

First, this is my property wrapper. I introduced a protocol because I'd like to support not only `Decimal` but also `Dictionary` or `Enum` containing `Decimal`.

```swift
protocol PreciseDecimalWrappedValue: Codable {
    associatedtype StorageType: Codable

    func toStorageValue() -> StorageType
    // This throws when we get a value of StorageType but fail to convert it.
    static func fromStorageValue(_ storageValue: StorageType) throws -> Self
}

@propertyWrapper
struct PreciseDecimal<T: PreciseDecimalWrappedValue>: Codable {
    var wrappedValue: T

    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        let storageValue = try container.decode(T.StorageType.self)
        self.wrappedValue = try T.fromStorageValue(storageValue)
    }

    func encode(to encoder: Encoder) throws {
        try wrappedValue.toStorageValue().encode(to: encoder)
    }
}

```

Below is an example usage:

```swift
struct SomeError: Error {
}

extension Dictionary: PreciseDecimalWrappedValue where Key: Codable, Value == Decimal {
    typealias StorageType = [Key: String]

    func toStorageValue() -> StorageType {
        mapValues { decimal in
            decimal.description
        }
    }

    static func fromStorageValue(_ storageValue: StorageType) throws -> Self {
        try storageValue.mapValues { stringValue in
            guard let decimal = Decimal(string: stringValue) else {
                throw SomeError()
            }
            return decimal
        }
    }
}

// Test code

func encodeAndDecode<T: Codable>(_ t: T) throws -> T {
    let data = try! JSONEncoder().encode(t)
    let value = try JSONDecoder().decode(T.self, from: data)
    return value
}

struct Test1: Codable {
    @PreciseDecimal var x: [String: Decimal] = ["a": 1.1, "b": 2.2, "c": 3.3]
}

print(try encodeAndDecode(Test1()).x)

```

I use `SingleValueContainer` in the property wrapper. According to the doc, it should only be used for primitive type. I'm not sure if `Dictionary` or `Enum` is considered as primitive type or not (does anyone know it?), but my experiments show the code works fine with them. That said, I suspect it's the use of `SingleValueContainer` that causes the issue with `Decimal?`. Please read on.

To support using the property wrapper with `Decimal?`, I added the following code:

```swift
extension Decimal: PreciseDecimalWrappedValue {
    typealias StorageType = String

    func toStorageValue() -> StorageType {
        self.description
    }

    static func fromStorageValue(_ storageValue: StorageType) throws -> Self {
        guard let decimal = Decimal(string: storageValue) else {
            throw SomeError()
        }
        return decimal
    }
}

extension Optional: PreciseDecimalWrappedValue where Wrapped: PreciseDecimalWrappedValue {
    typealias StorageType = Optional<Wrapped.StorageType>

    func toStorageValue() -> StorageType {
        switch self {
        case .none:
            return .none
        case .some(let wrapped):
            return .some(wrapped.toStorageValue())
        }
    }

    static func fromStorageValue(_ storageValue: StorageType) throws -> Self {
        switch storageValue {
        case .none:
            return .none
        case .some(let wrapped):
            return .some(try Wrapped.fromStorageValue(wrapped))
        }
    }
}

```

But unfortunately it fails to decode nil value.

```swift
struct Test2: Codable {
    @PreciseDecimal var x: Decimal?
}

// this works fine
print(try encodeAndDecode(Test2(x: 10)).x)

// this crashed with "Expected Optional<String> but found null value instead." error
print(try encodeAndDecode(Test2(x: nil)).x)

```

There are two parts involved with regard to the failure. The first part is the encoding part, which encodes `Test2(x: nil)` to `{"x":null}`. The second part is the decoding part, which generates the error.

I think I understand the encoding part. Codable synthesizer discards a `nil` property value by default when doing encoding. But according to explanation in [this](https://forums.swift.org/t/using-property-wrappers-with-codable/29804/4) thread, using property wrapper has an unexpected side effect: from the codable synthesizer's perspective, it changes an optional property to a non-optional property. That, plus the fact that I use `SingleValueContainer` in the property wrapper, is the reason I got `{"x":null}`, instead of `{}`.

I don't understand the failure in the decoding part. The error message suggested that the codable synthesizer can't decode `null` to `Optional<String>`. This is something I'm not sure, because from my understanding `SingleValueContainer` should be able to decode `null` to `Optional<String>`. Actually the `null` value in JSON data is the `SingleValueContainer` encoding output of nil value of `Optional<String>` type. _How come it only works one way and not the other way around? Could this be a bug?_

Now let's suppose the decoding failure isn't a bug, the question is if there is any way to generate `{}`, instead of `{"x":null}`, when using property wrapper? **EDIT** : on a second thought, even if I could find something way to generate `{}`, it wouldn't work with `SingleValueContainer` because the decoding would still fail - `SingleValueContainer` expect some input, instead of nothing, when doing decoding. So the issue is unsolvable with `SingleValueContainer`. On the other hand, it's not obvious how I can use `KeyedDecodingContainer` in my property wrapper.

I'm currently using this work around (I find it in the links I mentioned above). But I wonder if it's possible to avoid the issue in the first place?

```swift
extension KeyedDecodingContainer {
    func decode<T: ExpressibleByNilLiteral>(_ type: PreciseDecimal<T>.Type, forKey key: K) throws -> PreciseDecimal<T> {
        if let value = try self.decodeIfPresent(type, forKey: key) {
            return value
        }
        return PreciseDecimal(wrappedValue: nil)
    }
}

```
