Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional property wrapper used in Codable struct fails when value is missing

Tags:

swift

I've got a struct with a Double property that is represented as a String in the JSON coming from the backend.

struct Test: Codable {
    @StringRepresentation
    var value: Double?
}

Instead of implement init(from:) I've created the following property wrapper that takes advantage of LosslessStringConvertible to convert to and from String

@propertyWrapper
struct StringRepresentation<T: LosslessStringConvertible> {
    private var value: T?

    var wrappedValue: T? {
        get {
            return value
        }
        set {
            value = newValue
        }
    }
}

extension StringRepresentation: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if container.decodeNil() {
            value = nil
        } else {
            let string = try container.decode(String.self)
            value = T(string)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let value = value {
            try container.encode("\(value)")
        } else {
            try container.encodeNil()
        }
    }
}

This works for

{
  "value": "12.0"
}

and

{
  "value": null
}

but fails when the property is missing

{
}

giving the error

▿ DecodingError
  ▿ keyNotFound : 2 elements
    - .0 : CodingKeys(stringValue: "value", intValue: nil)
    ▿ .1 : Context
      - codingPath : 0 elements
      - debugDescription : "No value associated with key CodingKeys(stringValue: \"value\", intValue: nil) (\"value\")."
      - underlyingError : nil

I'm guessing this happens because the underlying StringRepresentation is not Optional. How can I make it optional?

Edit: Also, I have to encode the object as

{
"value": null
}

i.e the value can't be omitted.

like image 551
Lescai Ionel Avatar asked Oct 14 '19 08:10

Lescai Ionel


People also ask

What does Codable mean in Swift?

Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.

What is a property wrapper Swift?

What is a property wrapper in Swift? A property wrapper is a generic structure that encapsulates read and write access to the property and adds additional behavior to it.


1 Answers

Use an extension on KeyedDecodingContainer and add an overload for decode method like this:

extension KeyedDecodingContainer {

    func decode<T>(_ type: StringRepresentation <T?>.Type, forKey key: Self.Key) throws -> StringRepresentation <T?> where T : Decodable {
        return try decodeIfPresent(type, forKey: key) ?? StringRepresentation <T?>(wrappedValue: nil)
    }
}

This will make sure that your synthesised property _value which is a non-optional is always created but in case the key is not present in json it only wraps around a nil value.

This works with synthesised Codable initialiser as this overloaded decode is used instead of its generic counterpart because it has a concrete type which is preferred over a generic one if present.

like image 165
Pranshu Avatar answered Feb 07 '23 21:02

Pranshu