I recently built DeepCodable
, a package to encode and decode arbitrarily-nested data into flat Swift structs, by defining the coding paths with a result builder. I personally have been wanting something like this for a long time when interacting with third-party APIs, so I decided to build it.
As a concrete example, if you wanted to decode some nested JSON like this:
{
"a": {
"b": {
"c": {
"d1": {
"e1": "Some value"
},
"d2": {
"e2": {
"f2": "Other value"
}
}
}
}
}
}
Your only option today with normal Codable
(without writing a custom init(from:)
implementation) looks something like this:
struct SomeObject: Codable {
struct A: Codable {
struct B: Codable {
struct C: Codable {
struct D1: Codable {
let e1: String
}
struct D2: Codable {
struct E2: Codable {
let f2: String
}
let e2: E2
}
let d1: D1
let d2: D2
}
let c: C
}
let b: B
}
let a: A
}
That's certainly expressive of the underlying structure of the data, but it's a lot of nested type definitions and instances created for what amounts to decoding two values. And, in my experience with data like this, you often want to pull the actual values into a flat object anyways, meaning you end up having to write another layer of types to translate between the two structures.
With DeepCodable
, you can instead write something like this:
struct SomeObject: DeepCodable {
static let codingTree = CodingTree {
Key("a") {
Key("b") {
Key("c") {
Key("d1") {
Key("e1", containing: \._e1)
}
Key("d2") {
Key("e2") {
Key("f2", containing: \._f2)
}
}
}
}
}
}
@Value var e1: String
@Value var f2: String
}
To me, this is a lot more expressive of how the object should map to its serialized version, and lets you work in Swift with the decoded values much more easily.
This topic has come up many times over the years, and all the proposed or existing solutions I could find tried to overload the meaning of .
in a coding key to mean a sub-key, where dots can absolutely be part of valid literal keys in JSON (and many other formats). In addition, if you have multiple fields you want to encode/decode at the bottom of some deep hierarchy, you end up rewriting the intermediate paths multiple times instead of expressing your coding paths in a format closer to the actual structure.
Here are some of the key features of DeepCodable
:
- Provides custom
init(from:)
andencode(to:)
implementations- Compatible with all existing encoders and decoders, not just JSON
- Actual values get decoded using completely normal
Codable
semantics- If you're trying to decode a normal
Codable
object at the bottom (or anotherDeepCodable
object!), it will just work
- If you're trying to decode a normal
- If using just
DeepDecodable
orDeepEncodable
, don't interfere with the other direction of encoding (Encodable
orDecodable
, respectively)- You can absolutely decode something from a deeply nested representation and then right back to the normal flattened
Encodable
representation, or vice-versa
- You can absolutely decode something from a deeply nested representation and then right back to the normal flattened
- Conformance can be added by an inheriting protocol by providing the
codingTree
static property- For instance, I built a GitHub GraphQL client on top of
DeepCodable
, which can both generate a GraphQL query string and thecodingTree
from a result builder representation of the GraphQL query structure, allowing automatic decoding of GraphQL responses
- For instance, I built a GitHub GraphQL client on top of
Anyways, would love to see what the community thinks, and get any feedback you might have!