4

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.

Zaphod
  • 6,758
  • 3
  • 40
  • 60
  • Did you try to declare `var bar : Any?` Because it can be any, as you describe it. – Roman Ryzhiy Sep 17 '20 at 12:36
  • 1
    That's not what I want, unfortunately. I only want the field to be filled when it's an `Int`. Moreover, I really do not want to leave strong type by setting `Any` everywhere. – Zaphod Sep 17 '20 at 12:49

1 Answers1

9

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.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • That's a clever idea indeed. But I was lookin for something more general on the decoding side, without having to touch the model structures. But I like it anyway. – Zaphod Sep 17 '20 at 12:57
  • 1
    Thanks for your time... The second one also clever even if you have to write it for every type, and as you say is app-wide. – Zaphod Sep 17 '20 at 13:52