[SOLVED] How to see all "ancestors" of a recursive data model?

Issue

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?

Solution

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

Answered By – Rob Napier

Answer Checked By – Pedro (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.