Skipping errors while decoding JSON

JSON:

{
    "name": "hello",
    "age": null,
    "sex": "male"
}

MODEL:

struct Person: Codable {
    var name = ""
    var age = 0
    var sex = "male"
}

then:

Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "age", intValue: nil)], debugDescription: "Expected Int value but found null instead.", underlyingError: nil))

Can you make a mistake and go ahead? Why can't you go ahead?

Codable is the most difficult JSON parsing tool!!!!!!!!

1 Like

This works:

struct Person: Codable {
    var name: String
    var age: Int?
    var sex: String // probably the safest way to model this nowadays
}

let json = """
    {
        "name": "hello",
        "age": null,
        "sex": "male"
    }
    """
let person = try? JSONDecoder().decode(Person.self, from: Data(json.utf8))

Your definition of Person looks like this:

struct Person: Codable {
    var name = ""
    var age = 0
    var sex = "male"
}

Here, age is inferred to have the non-optional Int type – in other words, it has to be in the JSON, that’s why you get the error. (On the other hand, the synthesized decoding code could be smart enough to use the default value when not present in the JSON. @itaiferber, is there a reason for it not doing that?)

Codable is the most difficult JSON parsing tool!!!!!!!!

:heart: Swift is fairly strict, yes. It means more difficulties upfront and less difficulties in the long run. Sometimes it takes time to adjust to this tradeoff, but it’s worth it.

3 Likes
struct Person: Codable {
    var name = ""
    var age: Int? = 0
    var sex = "male"
}

Changing age property type to optional makes this work. Learn more about optionality and this will make sense to you.

Jeez, it just made you write single Codable conformance and that's it. U didn't have to write any line of code beside it. IMO not bad at all.

but i don't confirm witch field is optional....

when you state
var age = 0
compiler infers the type to be Int. 0 is Int.
But in JSON age is null, which can't be represented by Int. So decoding fails.

@alikvovk @zoul
It has been resolved to copy the source code, and then change the place where the exception is thrown to return to the default value.

I have no idea what really happened, but I’m glad it works now :)

You've covered the other aspects of this question, but to answer this specifically:

Yes, largely the ambiguity of what default values might mean for decoding. For instance,

struct Widget: Codable, Equatable {
    var name = "Default"
    var identifier = UUID()
}

could have default values for the sake of synthesizing an init(name:identifier:), or it could be trying to indicate that if, say name were missing from the payload, "Default" would be a safe default value. The compiler can't guess at the intent here, and you can see the potential danger with falling back to identifier's default value: if the data is corrupted or missing, the Widget would silently "succeed" decoding with a completely new identity (and in this case, a universally unique one), and there'd be no way to recover from this. [There is no way to distinguish, after the fact, "was this value generated as the default, or was it correctly decoded from the payload?"]

It's significantly safer to fail the decoding process rather than allow silent data corruption to propagate in surprising ways — if developers prefer to use the default values, it's always possible to customize the behavior.

(See also some pre-forums discussion on this topic at [swift-evolution] [Request for Feedback] Providing defaults for <Codable> reading and writing.)

2 Likes

May be it should be done like with CodingKeys? For example:

struct Widget: Codable, Equatable {
    enum CodingDefaults: CodingDefault {
        static let name = "Default"
        static let identifier = UUID()
    }
    var name: String
    var identifier: UUID
}

It seems like it simple enough to support it in code generation. Did you considered such option?

This is one way to spell it, yes. In general, there have been other considerations for "how do I override the behavior of decoding one property without having to override the whole initializer?", and adaptors have come up as a possible solution (e.g. offering ways to customize decoding on a per-property basis, allowing post-decoding transformations, etc.). It's reasonable that we could also spell "please use this default value" as one such adaptor.

However, what adaptors for Codable properties might look like, and what the feature would look like with Swift's current (or future) capabilities has not been fully explored.

One issue with an approach like this is that it's barely more convenient that writing out the full implementation explicitly (and not relying on synthesis in this class). Writing out the implementation has the benefit of making it 100% clear what your intentions are.

The only really hard part about "manual" implementation is remembering that it's not that hard to do. :slightly_smiling_face:

1 Like

this is that it's barely more convenient that writing out the full implementation

I disagree. Example above is very simple, with only two properties. Imagine case with several dozens of properties, and there is one array that is optional in json, but you have default value – empty array. You can write many lines of code, or three:

struct User: Codable, Equatable {
    enum CodingDefaults: CodingDefault {
        static let friends: [Friend] = []
    }
    let friends: [Friend]
    ....
}

The only really hard part about "manual" implementation is remembering that it's not that hard to do. :slightly_smiling_face:

We have hundreds or may be thousands loc of manual implementations. Yes, it is not hard, and very flexible. But sometimes such fallback seems very disappointing for small customisations. And sometimes, instead of implementing it manually, it just easier to reflect json structure and deal with its problems elsewhere.

But, I agree with itaiferber, it is not very appealing to constantly adding such customisation points at compiler level. Looks like more general approach must be taken.

2 Likes

This is pretty condescending; it's nice for you to help other people out, but please try to be patient with them when they're working through new ideas.

5 Likes