如何将递归数据映射到结构中?

Posted

技术标签:

【中文标题】如何将递归数据映射到结构中?【英文标题】:How to map recursive data into structure? 【发布时间】:2020-09-30 15:24:00 【问题描述】:

这篇文章与previous post I made相关。我希望映射以下嵌套字典:

["A": [["A1": ["A11", "A12"]], ["A2": ["A21", "A22"]]],
   "B": [["B1": ["B11", "B12"]], ["B2": ["B21", "B22"]]]
    ]

进入递归结构:

Item(title:"",children:
    [Item(title:"A",children:
        [Item(title:"A1", children:
            [Item(title:"A11"),Item(title:"A12")]
            )]),
          Item(title:"B",children:
            [Item(title:"B1"),Item(title:"B2")]
        )]
)

struct Item: Identifiable 
    let id = UUID()
    var title: String
    var children: [Item] = []

为了实验,我从 ["A": [["A1": ["A11"]]] 开始,做了一个 json 字符串:

let json1: String = """
            "title": "", "children":["title": "A",
                                      "children": ["title": "A1",
                                                    "children": ["title": "A11"]
                                                    ]
                                     ]
            
"""



let decoder = JSONDecoder()
let info = try decoder.decode(Item.self, from: json.data(using: .utf8)!)
print(info)

只有当我在最后一个节点中包含 "children": [] 时它才有效,如下所示:

   let json2: String =  """
                "title": "", "children":["title": "A",
                                          "children": ["title": "A1",
                                                        "children": ["title": "A11", "children": []]
                                                    ]
                                     ]
            
"""

我需要做什么才能使json1字符串工作,这样即使没有children的输入,它也会采用[]的默认值?

【问题讨论】:

【参考方案1】:

您必须提供自定义init(decoder:) 来处理这种情况。处理此问题需要您使用JSONDecoder 的容器的decodeIfPresent API,并仅在值存在时尝试解码,并在条件失败时提供默认值。方法如下:

extension Item: Codable 
    enum CodingKeys: String, CodingKey 
        case title, children
    
    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decode(String.self, forKey: .title)
        children = try container.decodeIfPresent([Item].self, forKey: .children) ?? []
    

【讨论】:

我刚刚尝试了您的解决方案,谢谢,它有效。我是 swift 新手,只想知道 init(decoder: ) 在哪一行被调用?是在“let info = try decoder.decode(Item.self, from: json.data(using: .utf8)!)”中吗?怎么叫 是的,当您尝试解码时。编译器调用该方法进行解码。【参考方案2】:

一种方法是将children 设为可选并使用计算属性来访问它。在childrennil 的情况下,计算属性可以返回一个空数组。

struct Item: Identifiable, Decodable 
    let id = UUID()
    var title: String
    private var privateChildren: [Item]?
    var children: [Item] 
        return self.privateChildren ?? []
    

    enum CodingKeys: String,CodingKey 
        case title
        case privateChildren = "children"
    

我使用了CodingKeys 枚举,以便您可以在 JSON 和代码中保持相同的名称,但如果您在 JSON 或代码中更改了属性名称,则可以避免这种情况。

【讨论】:

我猜这里有些东西不起作用,它显示:Item(id: DEB3941C-F13C-425A-A34E-C1382B545E12, title: "", privateChildren: Optional([__lldb_expr_171.Item(id: D83563AA -5AF9-4B52-8480-9742178DC3BD,标题:“A”,privateChildren:可选([__lldb_expr_171.Item(id:B8163623-0A70-4C01-BA15-1148A42BA6FF,标题:“A1”,privateChildren:可选([__lldb_expr_171.Item (id: 05D09104-50BC-4050-AE43-5BF1754B2A29, title: "A11", privateChildren: nil)]))]))])) 我希望以“children”为key,最后一个privateChildren为nil但不是[] children 是一个计算属性,所以如果你只是print(item),它不会显示,但它会有你想要的值。试试print(item.children) 啊,明白了。对不起,这是一个严重的错误。最后一个孩子是 []: print(info.children[0].children[0].children[0].children) 显示 [].

以上是关于如何将递归数据映射到结构中?的主要内容,如果未能解决你的问题,请参考以下文章

将数据从 Firestore 映射到 Swift 中的结构 - IOS

如何从 Freemarker 中的模板数据映射对象递归打印数据?

将集合映射到 upsert 到数据库中。如何批量更新插入?

如何将 JOIN 映射到具有子结构数组的结构数组

可以将一系列键映射到值的数据结构

桶排序(Bucket Sort)