Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

General strategy to decode type mismatch keys in JSON into nil when optional in Swift

Here is my problem, when I receive some JSON, it happens that some values do not match the required type. I don't really mind, I'm only interested by the value when its type is correct.

For instance, the following structure:

struct Foo : Decodable {
    var bar : Int?
}

I'd like it to match these JSON:

{ "bar" : 42 }    => foo.bar == 42
{ "bar" : null }  => foo.bar == nil
{ "bar" : "baz" } => foo.bar == nil

Indeed I'm looking for an optional Int, so whenever it's an integer I want it, but when it's null or something else I want nil.

Unfortunately, our good old JSONDecoder raises a type mismatch error on the last case.

I know a manual way to do it:

struct Foo : Decodable {
    var bar : Int?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.bar = try? container.decode(Int.self, forKey: .bar)
    }
    
    enum CodingKeys : CodingKey {
        case bar
    }
}

But I have many structures, and many fields to check.

So I'd like to know if there is a general way to do it something like:

decoder.typeMismatchStrategy = .nilInsteadOfError // <= Don't try it at home, I know it does not exist...

Or maybe override JSONDecoder, anyway something to write once and not on every struct.

Thanks in advance.

like image 278
Zaphod Avatar asked Oct 27 '25 15:10

Zaphod


1 Answers

One approach would be to create a property wrapper that's Decodable to use for these these kind of properties:

@propertyWrapper
struct NilOnTypeMismatch<Value> {
    var wrappedValue: Value?
}

extension NilOnTypeMismatch: Decodable where Value: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = try? container.decode(Value.self)
    }
}

Then you could selectively wrap the properties that you want to special-handle:

struct Foo : Decodable {
    @NilOnTypeMismatch
    var bar : Int?
}

A more holistic approach would be to extend KeyedDecodingContainer for Ints, but that would apply app-wide:

extension KeyedDecodingContainer {
    func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? {
        try? decode(Int.self, forKey: key)
    }
}

Unfortunately, I don't think it's possible (or don't know how) to make it generic, since my guess is that this function overload is at a lower priority than a default implementation when using generics.

like image 153
New Dev Avatar answered Oct 29 '25 04:10

New Dev