I am using
JSONDecoder to produce a nested set of objects from a JSON string. Is there a way, in the
init(from decoder: Decoder) implementation on a nested class, to get a reference to where in the original JSON string it originates? This could be line number and column. Or equally useful to get the actual JSON snippet.
I have two use cases for this. Most important is when I use a trick to get "safe" decoding of arrays, like the
Safe type described here. The method is great to ensure we do not fail the entire decoding but only the one object in a large array. The limitation is that it is quite difficult to diagnose which object specifically failed.
Another use case is to keep a reference to the "source" in decoded objects.
Your implementation of
Safe could store a
Result instead of an optional success value. In the result, store the
DecodingError that prevented the value from being decoded in the first place.
From the set of
Safe decoded objects, the ones that contains a failure
Result will tell you everything that Swift decoding system can tell, with the
DecodingError.Context value that is attached to all kinds of
DecodingError. The context doesn't tell you anything about the physical location in the original JSON data. Instead, it tells you the
codingPath, an array of JSON object keys and array indexes. From this path, you can build a physical location in the original JSON data.
Look at this gorgeous screenshot from Pulse 2.0, where the erroneous value is highlighted in JSON text. There's no doubt
DecodingError gives you the information you need.
Also note how the maintainer of Pulse is the same @kean who wrote the blog post you are referring to ;-) Alex rocks
That looks really cool, no doubt. Going via the
DecodingError.Context as you suggest is certainly a viable way and I guess I can look at Pulse source to see how they do it.
I assume I would need to parse the JSON again using something like
JSONSerialization in Swift) in order to make sense of the
CodingPath, or perhaps better a streaming decoder for JSON.
This seems like a lot of code to get to a context that is already there in the
JSONDecoder, although hidden it seems.
Yes, it's probably a lot of code. And I'm not even sure
NSJSONSerialization will give you the physical location you're after Definitely, looking at the Pulse source will give hints.
The encoding/decoding system is designed to work with any kind of serialization (JSON, Property lists, database rows, whatever), and that's why
DecodingError does not expose anything that is specific to the underlying physical storage. Consider a database row, for example: it does not exist as a flat buffer of bytes, but rather some kind of dictionary, or a bunch of index or key-based functions.
Thanks for the shoutout, @gwendal.roue!
The code for rendering JSON in Pulse isn't pretty, but it gets the job done.
JSONDecoder to try and decode the data and record an error if decoding fails.
DecodingError contains a
codingPath (an array of CodingKey values). A key can be either a
String (in case it's an object) or an
Int (in case it's an array). By using the coding path, it's easy to find the values that resulted in an error.
I don't map the error back to the original file and the exact line or position in Pulse. I use
JSONSerialization.jsonObject(with:options:) to create dynamic json objects (
Any) and then render them recursively while remembering the coding path. If the current coding path matches the coding path from the error, I highlight the rendered text. Finding the position in the original file is going to be significantly more complicated.
P.S. yes, @gahms, sounds like you need Pulse