How do I customise the behaviour of JSONDecoder for primitive
types like Int, Bool?
Here is the problem:
Backend cannot be relied upon for types. Eg: Bool can come as true/false or "true"/"false"(bool can come wrapped in double quotes)
We have at least 300 Codable structs having average 15 properties in them and writing decoding logic for all of them is cumbersome. Also the logic remains more or less same hence the code has become repetitive
Hence, I am looking for a solution such that if there is a Type mismatch
then primitive types should be able to handle it and if not then it should be set to nil
if that type is Optional.
struct SafeBool: Codable {
private var bool: Bool?
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let b = try? container.decode(Bool.self) {
self.bool = b
} else if let string = try? container.decode(String.self) {
if string.lowercased() == "true" {
self.bool = true
} else if string.lowercased() == "false" {
self.bool = false
} else {
throw Error()
}
}
}
}
This, although solves the problem, but creats unnecessary confusion among fellow developers as Wrapped types do not come out as naturally as the Native ones. Also the value cannot be accessed directly (it always need xyz.bool) to extract the raw value
JSONDecoder
protocol KKDecodable: Decodable {
init(decoder1: Decoder)
}
extension Bool: KKDecodable {
init(decoder1: Decoder) {
// Logic for creating Bool from different types
}
}
class JSONDecoder1: JSONDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : KKDecodable {
// Some code that would invoke `init(decoder1: Decoder)`
// which is defined in `KKDecodable`
}
}
I was not able to write working code using this method
You can use property wrapper. Imagine this as an example:
@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)
}
}
}
struct MyType: Decodable {
@SomeKindOfBool var someKey: Bool?
}
You can use someKey
value like a normal Bool
now:
let jsonData = """
[
{ "someKey": "something else" },
{ "someKey": "true" },
{ "someKey": true }
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey)
}
nil
Optional(true)
Optional(true)
You can do similar for other situations and also any other type you need. Also note that I have changed the code to match your needs, but you can use the more compatible version that I posted as a gist here in GitHub