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.
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 in Swift? A property wrapper is a generic structure that encapsulates read and write access to the property and adds additional behavior to it.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With