Hi @cal,
Thanks for making this pitch. It brings to light the fact we can improve the ergonomic of decoding deeply nested, large payloads that are encoded by an external party. We realize that such decoding scenarios are common on both server and client where you have to deal with data coming from external sources, commonly JSON from RESTful HTTP services, and would like to see it solved.
@Tony_Parker and myself discussed this further, and the ergonomic issue we see is that it can be fairly heavy handed to need and define the entire data structure hierarchy when the path to the data you want to extract is well known. For example, given the contrived JSON:
{ "a": { "b": { "c": { "d": { "e": { "f": { "g": { "h": { "i": { "foo": 42, "bar": true }}}}}}}}}}
It could be nice to only need and declare:
struct Payload: Codable {
var foo: Int
var bar: Bool
}
And be able to decode it directly, given that we know the path to the payload is “a.b.c.d.e.f.g.h.i”. For completeness, the achieve this today you need to declare the entire hierarchy:
struct Payload: Codable {
var a: A
struct A: Codable {
var b: B
struct B: Codable {
var c: C
struct C: Codable {
var d: D
struct D: Codable {
var e: E
struct E: Codable {
var f: F
struct F: Codable {
var g: G
struct G: Codable {
var h: H
struct H: Codable {
var i: I
struct I: Codable {
var foo: Int
var bar: Bool
}
}
}
}
}
}
}
}
}
}
and access it via the type-safe accessors:
let payload = JSONDecoder().decode(Payload.self, from: data)
let i = payload.a.b.c.d.e.f.g.h.i
The main advantage of the current API is that it puts emphasis on type-safety. As such, we don’t feel string based key parsing (as proposed) is the way to go, given the following pitfalls with this (some of which have been pointed out on the thread):
- It introduces complexity into understanding
Codable
because there are now two kinds of keys (CodingKeys
and CodingKeyPaths
).
- If and when the decoding requirements expand and the user needs more values, it’s tempting to continue down the string-based path instead of using strong-types, or IOW it subtly encourages users to prefer short term convenience over type-safety.
That said, if we narrow the proposal to focus on the initial axis into the data, i.e. in such cases that the data starts at some well know deeply nested path, then we could probably solve this with a higher level API on the concrete Decoder itself. to use the example above, something like:
let payload = JSONDecoder().decode(Payload.self, from: data, at: "a.b.c.e.f.g.h.i")
Where that last argument is a CodingKeyPath
type which is expressible by string literal. That reduces the scope of the change significantly and avoids the pitfalls mentions above. The last argument will do the string parsing before giving JSONDecoder
an Array-like type, leaving the complexity outside the decoder itself.
We should further discuss where this higher level API belongs - the concrete Decoders or somewhere else, but we suspect this problem is more common in JSON compared to other Codable formats, so a JSON centric solution could potentially suffice.