Sure, consider this kind of common response:
let json = Data(#"""
{
"response": {
"results": [{
"name": "Alice",
"age": 43
}],
"count": 1
},
"status": 200
}
"""#.utf8)
struct Person: Decodable {
var name: String
var age: Int
}
We just want the results, which is [Person]. We can't change how [Person] is decoded. So the common approach is to make a wrapper (AnyCodingKey is ExpressibleByStringLiteral):
struct PersonResponse: Decodable {
var results: [Person]
init(from decoder: Decoder) throws {
self.results = try decoder.container(keyedBy: AnyCodingKey.self)
.nestedContainer(keyedBy: AnyCodingKey.self, forKey: "response")
.decode([Person].self, forKey: "results")
}
}
This doesn't exist for any reason except to pull apart the response. It is completely possible to make this generic if there are several responses with the same structure, but if the structures are more adhoc, it's kind of annoying to make the extra layer.
By escaping the Decoder, the init(from:) can become just a function rather than a whole type.
let decoder = try JSONDecoder().decoder(for: json)
try decoder.container(keyedBy: AnyCodingKey.self)
.nestedContainer(keyedBy: AnyCodingKey.self, forKey: "response")
.decode([Person].self, forKey: "results")
I have other approaches that I'm exploring to improving ad-hoc decoding, but often a handy thing was to get my hands on a Decoder, and the only way I can do that is with a top-level type. That said, the wrapper type hasn't been my biggest problem. It was just something I was exploring how to remove.
Passing parameters is a very early exploration into tracking recovered errors, and providing configuration (for example, formatters). userInfo is very ugly to use. It has no type safety and there's no way to make values required. I don't have any clear question here; I'm still exploring. I just didn't want to get too far down the "escape a Decoder" road without checking its legitimacy.
-Rob