如何查看递归数据模型的所有"ancestors"?

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

我收到了 JSON:

{
    "categories":
    [
        {
            "category_name": "example name",
            "children":
            [
                {
                    "category_name": "example name"
                },
                {
             ...

可以看出,数据是一种递归格式。我能够编写将其解码为我的自定义类型的代码,即:

 struct Name: Codable {
        let cat: String
        let children: [cat]?
}

现在,对于任何一只猫,我都想知道它的“路径”。比如,我想知道所有的超级(祖先)类别是什么。因此,对于类别“平板电脑”,我希望能够遍历向下钻取结构的样子,在这种情况下可能如下所示:

Electronics -> Computers -> Laptops and Tablets -> Tablets

如何构造我的代码或数据模型以便能够检索任何类别的此信息?

这是一个基本的递归解决方案,它将元素的路径添加到数组中,根元素作为第一个,目标元素作为最后一个。

它使用 contains() 所以 Category 需要符合 Equatable 或者可以更改为使用 `contains(where:) 而不是

contains(where: { [=10=].categoryName == target.categoryName })

如果是更实用的解决方案。

func extractChain(for target: Category, parent: Category, chain: inout [Category]) {
    guard let children = parent.children else {
        chain = []
        return
    }

    chain.append(parent)

    if children.contains(target) {
        chain.append(target)
        return
    }

    for category in children where category.children != nil {
        extractChain(for: target, parent: category, chain: &chain)
        if !chain.isEmpty { return }
    }
    chain = [] // No match, clear the array
}

我只进行了一些基本测试,一项匹配到第 3 级以下,一项没有匹配,因此可能需要进一步测试。

您可以像这样使用 class 和祖先 :

class Category: Codable {
    let category_name: String
    let children: [Category]?
    var ancestor : Category?

    func setupAncestor(ancestor: Category?) {
        self.ancestor = ancestor
        if let children = children {
            for child in children {
                child.setupAncestor(ancestor: self)
            }
        }
    }

    func ancestors() -> [Category] {
        if let ancestor = ancestor {
            var ancestorList = [ancestor]
              ancestorList.append(contentsOf: ancestor.ancestors())
             return ancestorList
        }
        return []
    }
    
    func ancestorsNames() -> [String] {
        if let ancestor = ancestor {
            var ancestorList = [ancestor.category_name]
              ancestorList.append(contentsOf: ancestor.ancestorsNames())
             return ancestorList
        }
        return []
    }
    
    func displayAncestors() {
        for ancestor in ancestors() {
            print("Ancestor : \(ancestor.category_name)")
        }
    }
    func display(_ level: Int) {
        print("Level \(level)")
        print("Category : \(category_name) \(ancestorsNames())")
        //displayAncestors()
        if let children = children {
            for child in children {
                child.display(level+1)
            }
        }
    }
}

struct Categories : Codable {
    let categories: [Category]
    func setupAncestors() {
        for category in categories {
            category.setupAncestor(ancestor: nil)
        }
    }
}

let json = "..."
let jsonData = json.data(using: .utf8)

let decoder = JSONDecoder()

do {
    let categories = try decoder.decode(Categories.self, from: jsonData!)
    categories.setupAncestors()
    for category in categories.categories {
        category.display(0)
    }
    
} catch {
    print("Json decode error : \(error)")
}

您可以更改顺序 and/or return 列表中只有 categoryName

编辑:更正了代码

首先,您需要将路径添加到“类别”,以便有地方存储数据。为了方便起见,我还将添加一个 CategoriesResponse 来处理 top-level 结构,但这并不重要:

struct CategoriesResponse: Decodable {
    var categories: [Category]
}

struct Category {
    let path: [String]
    let categoryName: String
    let children: [Category]
}

(我假设你想要的只是父类别的名称。如果你想要某种引用,那是可能的,但数据结构会变得有点复杂。这种基本方法仍然有效,不过。如果您需要类似的东西,请告诉我,我可以扩展答案。)

当然还有标准的 CodingKeys 东西:

private enum CodingKeys: String, CodingKey {
    case categoryName = "category_name"
    case children
}

解决方案的核心是您需要一个可以接受 KeyedDecodingContainer(而不是解码器)和路径的 init,并处理解码其他所有内容。

// 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
}

最后,您需要一个 Decodable 实现来启动它:

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: [])
    }
}

有了它,它应该使用标准解码器按预期工作:

let categories = try JSONDecoder().decode(CategoriesResponse.self, from: json).categories