We have a tree-based data model that is based around the Codable
protocol. The root of the tree holds its immediate children, as well as a reference to all children in the hierarchy, as seen here...
Root
|
|-->Children
| |
| |-->Item 1
| |-->Item 2
| | |
| | \-->Children
| | |
| | \-->Item 3
| |
| \-->Item 4
| |
| \-->Children
| |
| \-->Item 5
| |
| \-->Children
| |
| \-->Item 6
|
\-->AllChildren <<-- Not Serialized!!
|
|-->Item 1
|-->Item 2
|-->Item 3
|-->Item 4
|-->Item 5
\-->Item 6
Now the AllChildren
part isn't serialized as they are just references to the actual instances from above.
To make the above work, we need to populate AllChildren
programmatically, as those children are being decoded. However, we aren't sure how to pass the Root
object into the children's init(from:Decoder)
calls to handle that as we don't see any way to pass additional state data into the decoding chain. It seems the only information you have available is the Decoder
, which you don't control.
Our work-around is inside the init(from:Decoder)
of the Root
, once it's done decoding/initializing all of its children, it then re-crawls the entire hierarchy, slurping up the children, but I really hate that I have to re-crawl the hierarchy after just doing it during the init(from:Decoder)
pass.
So is there a way to pass additional state information into the Decode
process, preferably as something on the Decoder
handed to the init(from:Decoder)
calls?
You can assign an arbitrary set of key/value pairs to the decoder's userInfo
and read them in the init
methods.
Note that the keys to userInfo
are CodingUserInfoKey
, but you can create those from strings using CodingUserInfoKey(rawValue: "key")!
.
EDIT: After a few years, I wanted to update this with a bit better solution, which is to create a custom init
that accepts a Decoder and use that to pass along state. (You hint at this in the comments, and it really is as easy as that.)
I haven't tested this carefully, and I'm not quite certain the JSON you're using, but I think this approach should make how to adapt it clear.
// This works for either structs or classes
struct Item {
var value: Int
var children: [Item]
enum CodingKeys: CodingKey { case value, children }
// This is not the normal Decodable. In fact, Item is not Decodable at all
// It's just an init that takes extra state
init(from decoder: Decoder, allChildren: inout [Item]) throws {
// Decode the normal stuff
let container = try decoder.container(keyedBy: CodingKeys.self)
self.value = try container.decode(Int.self, forKey: .value)
// Decode children by hand
let childrenContainer = try container.nestedUnkeyedContainer(forKey: .children)
var children: [Item] = []
while !childrenContainer.isAtEnd {
// For each child, pass along the `allChildren` array
children.append(try Item(from: decoder, allChildren: &allChildren))
}
self.children = children
// Append self to the list. This creates a depth first search order.
// Item in this case is a struct, so this is a copy. If Item were a class
// then this would be a reference.
allChildren.append(self)
}
}
struct Root: Decodable {
var children: [Item]
var allChildren: [Item]
// Basically the same as Item, but creates the `allChildren` first.
init(from decoder: Decoder) throws {
var allChildren: [Item] = []
let childrenContainer = try decoder.unkeyedContainer()
var children: [Item] = []
while !childrenContainer.isAtEnd {
children.append(try Item(from: decoder, allChildren: &allChildren))
}
self.children = children
self.allChildren = allChildren
}
}