JSONCodable icon indicating copy to clipboard operation
JSONCodable copied to clipboard

Improve error messages

Open tomlokhorst opened this issue 8 years ago • 5 comments

Preface: I've looked at a whole bunch of different JSON libraries for a talk I gave last week on JSON parsing in Swift. The talk was partly about different libraries, and partly about the JsonGen code generator I've build. Although I didn't mention JSONCodable in the talk, it is in my opinion one of the best JSON libraries I've looked at.

One of the very few features I miss in JSONCodable is verbose error messages. In JsonGen I try to include as much information as possible about errors in the JSON. So it's easier to debug problems in JSON coming from the server.

These two features in particular:

  1. Include the complete "path" of the error.
  2. Collect all errors, instead of stoping at the first error.

From my post on error messages, these are the types of error messages JsonGen generates:

2 errors in Blog struct
 - subTitle: Value is not of expected type String?: `42`
 ▿ posts: 2 errors in array
    ▿ [1] 1 error in Post struct
       - title: Field missing
    ▿ [2] 3 errors in Post struct
       - title: Value is not of expected type String: `1`
       - body: Value is not of expected type String: `42`
       ▿ author: 2 errors in Author struct
          - name: Field missing
          - email: Field missing

Feel free to close this issue if this isn't something you're interested in for JSONCodable. It's just something I wanted to bring to your attention.

tomlokhorst avatar Jan 22 '16 10:01 tomlokhorst

For reference; I plan on separating the "library" part from the "code generator" part of JsonGen. So that users can use the library without the code generator. Or maybe change the code generator to generate decoders for other libraries.

JSONCodable is very interesting to me, if it includes all the features that are currently in the JsonGen library, I could even consider dropping my own library en using only JSONCodable as a target library for the code generator.

Perhaps of interest, my JsonDecodeError enum is very similar to your JSONDecodableError.

tomlokhorst avatar Jan 22 '16 10:01 tomlokhorst

Providing more verbose error messages is definitely a interesting goal. I could see why there might be some issues implementing this though. Any ideas?

matthewcheok avatar Jan 23 '16 18:01 matthewcheok

I think this can be implemented by storing a dictionary of errors in the JSONDecoder class:

var errors: [String: JSONDecodableError] = [:]

The decode functions can then be changed to store JSONDecodableError and return optionals.

The new decodable init can be something like:

extension Company: JSONDecodable {
  init(object: JSONObject) throws {
    let decoder = JSONDecoder(object: object)
    guard let
      name: String = try decoder.decode("name"),
      address: String? = try decoder.decode("address")
    else { throw JSONDecodableError.MultipleErrors(decoder.errors) }

    self.name = name
    self.address = address
  }
}

I haven't implemented this. So it isn't a complete design, but I think it's roughly something in this direction.

tomlokhorst avatar Feb 16 '16 07:02 tomlokhorst

Just throwing this out there but it would be pretty cool if to indicate where in the object graph the error occurred. Maybe like:

JSONError([
  [index: 0, name: JSONDecodableError.IncompatibleType(type: Int, expected: String)],
  [index: 10, asset: [url: JSONDecodableError.IncompatibleType(type: Int, expected: String)]]
])

matthewcheok avatar Feb 16 '16 07:02 matthewcheok

I realised this when implementing this in JsonGen: The guard let is shortcutting. So all decoders have to be called separately, to be able to collect all errors:

extension Company: JSONDecodable {
  init(object: JSONObject) throws {
    let decoder = JSONDecoder(object: object)

    let _name: String = try decoder.decode("name")
    let _address: String? = try decoder.decode("address")

    guard let name = _name, address = _address else {
      throw JSONDecodableError.MultipleErrors(decoder.errors)
    }

    self.name = name
    self.address = address
  }
}

The location in the object graph can be found by recursively following the errors in MultipleErrors.

tomlokhorst avatar Feb 23 '16 14:02 tomlokhorst