I am receiving a JSON:
{
"categories":
[
{
"category_name": "example name",
"children":
[
{
"category_name": "example name"
},
{
...
As can be seen, the data is a recursive format. I was able to write the code for decoding it into my custom type which is:
struct Name: Codable {
let cat: String
let children: [cat]?
}
Now, for any cat, I would like to know the "path" of it. As in, I'd like know what are all the super(ancestor) categories. So, for the category "tablets", I would like to be able to traverse what the drill down structure looks like, which in this case could look like:
Electronics -> Computers -> Laptops and Tablets -> Tablets
How do I structure my code or data model to be able to retrieve this information for any category?
First, you'll want to add the path to Category so you have somewhere to store the data. For convenience, I'll also add a CategoriesResponse just to handle the top-level structure, but it's not really important:
struct CategoriesResponse: Decodable {
var categories: [Category]
}
struct Category {
let path: [String]
let categoryName: String
let children: [Category]
}
(I'm assuming what you want are just the names of the parent categories. If you want references of some kind, that's possible, but the data structures get a little more complicated. This basic approach will still work, though. Let me know if you need something like that, and I can expand the answer.)
And of course standard CodingKeys stuff:
private enum CodingKeys: String, CodingKey {
case categoryName = "category_name"
case children
}
The meat of the solution is that you need an init that can accept a KeyedDecodingContainer (rather than a Decoder) and a path, and handle decoding everything else.
// For each element, decode out of the container by hand rather than recursing into init(from: Decoder)
private init(from container: KeyedDecodingContainer<CodingKeys>, path: [String]) throws {
// Track our own path up to this point
self.path = path
// Unload the usual stuff
self.categoryName = try container.decode(String.self, forKey: .categoryName)
// Construct the children, one element at a time (if children exists)
var children: [Category] = []
if container.contains(.children) {
// Extract the array of children
var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children)
while !childrenContainer.isAtEnd {
// Extract the child object
let childContainer = try childrenContainer.nestedContainer(keyedBy: CodingKeys.self)
// For each child, extend the path, decode
let child = try Category(from: childContainer, path: path + [self.categoryName])
// And append
children.append(child)
}
}
self.children = children
}
And finally, you need a Decodable implementation just to kick it all off:
extension Category: Decodable {
// Top level decoder to kick everything off
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(from: container, path: [])
}
}
With that, it should work as expected using a standard Decoder:
let categories = try JSONDecoder().decode(CategoriesResponse.self, from: json).categories