I'm using a property wrapper to decode the strings "true" and "false" as Booleans. I also want to make the key optional. So if the key is missing from the JSON, it should be decoded as nil. Unfortunately, adding the property wrapper breaks this and a Swift.DecodingError.keyNotFound
is thrown instead.
@propertyWrapper
struct SomeKindOfBool: Decodable {
var wrappedValue: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
switch stringifiedValue.lowercased() {
case "false": wrappedValue = false
case "true": wrappedValue = true
default: wrappedValue = nil
}
} else {
wrappedValue = try? container.decode(Bool.self)
}
}
}
public struct MyType: Decodable {
@SomeKindOfBool var someKey: Bool?
}
let jsonData = """
[
{ "someKey": true },
{ "someKey": "false" },
{}
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey ?? "nil")
}
Any idea how to resolve this?
The synthesized code for init(from:)
normally uses decodeIfPresent
when the type is optional. However, property wrappers are always non-optional and only may use an optional as their underlying value. That's why the synthesizer always uses the normal decode
which fails if the key isn't present (a good writeup in the Swift Forums).
I solved the problem by using the excellent CodableWrappers package:
public struct NonConformingBoolStaticDecoder: StaticDecoder {
public static func decode(from decoder: Decoder) throws -> Bool {
if let stringValue = try? String(from: decoder) {
switch stringValue.lowercased() {
case "false", "no", "0": return false
case "true", "yes", "1": return true
default:
throw DecodingError.valueNotFound(self, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected true/false, yes/no or 0/1 but found \(stringValue) instead"))
}
} else {
return try Bool(from: decoder)
}
}
}
typealias NonConformingBoolDecoding = DecodingUses<NonConformingBoolStaticDecoder>
Then I can define my decodable struct like this:
public struct MyType: Decodable {
@OptionalDecoding<NonConformingBoolDecoding> var someKey: Bool?
}