我如何解码 Swift 中的 JSON,它是一个包含双重嵌套项目的数组?
How do I decode JSON in Swift where it's an array with double-nested items?
假设 JSON 看起来像这样:
[
{
"data": {
"children": [
{
"name": "Ralph"
},
{
"name": "Woofer"
}
]
}
},
{
"data": {
"children": [
{
"name": "Spot"
},
{
"name": "Trevor"
}
]
}
}
]
你有这个非常奇怪的结构,其中根项是一个数组,有两个对象,这两个对象中的每一个都是 Dog
个字典的数组。
但问题是Dog
数组是两个key in!您必须通过 data
和 children
才能到达。我 描述了用一个键深度来完成它,但是当它嵌套两个深度时我似乎无法重现结果。
我希望结果(看起来很奇怪)是这样的,其中两个列表是分开维护的:
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
}
我知道我需要自定义 initializer/decoder,但我不确定如何访问它。
所以,简短的回答是:你不能,而长的回答更长。
tl;博士
蒙皮的一种方法是从结构的中间表示开始。像这样:
struct Intermediate: Codable {
struct Dog: Codable {
let name: String
}
struct Children: Codable {
let children: [Dog]
}
let data: Children
}
然后您可以将其转换为您的 Result
结构。您可以将 Result
结构转换为用于序列化的中间结构。这使您可以避免使用更复杂的配置键和编码器。如果你不想让任何人戳它,你可以在你的模块中保持中间表示私有。
使用中间结构进行垃圾回收并收集所需数据,然后处理它们。
因此,从在顶层声明的 Dog 结构开始:
struct Dog : Decodable { let name : String }
在您的实际代码中,制作临时本地结构来包装它并解码 JSON:
struct TheChildren : Decodable { let children : [Dog] }
struct TheData : Decodable { let data : TheChildren }
let arr = try! JSONDecoder().decode([TheData].self, from: yourJSONdata)
现在只需拉出想要的狗:
let dogs = arr.map {[=12=].data.children}
/*
[[Dog(name: "Ralph"), Dog(name: "Woofer")],
[Dog(name: "Spot"), Dog(name: "Trevor")]]
*/
那是狗数组的数组,所以两者 "arrays are maintained separately" 因为它们是结果数组的独立元素。这似乎是一个完全合理的表示。
现在,如果您想将该信息进一步填充到新结构中,没问题。它不会与您提出的结果结构相同,因为名称 dogs1
和 dogs2
没有出现在数据中,并且您不能在以下位置组成 属性 名称运行时(好吧,在 Swift 4.2 中你可以,但那是另一回事了)。但关键是,您已经轻松获得了 Dog 数据,并且没有额外的 material。并且没有真正的理由为什么访问名称 dogs1
下的第一个数组比通过索引获取它更好 dogs[0]
;事实上,后者实际上更好。以索引号结尾的 属性 名称 总是 难闻的气味,表明您真正需要的是某种集合。
您可以解码 JSON 而无需引入中间结构,同时通过将唯一键为 data
的外部 Dictionary
解码为嵌套 Dictionary
来保持类型安全属于 [String:[String:[Dog]]]
类型,这非常混乱,但可以工作,因为您在外部词典中只有 2 个嵌套层和单个键。
struct Dog: Codable {
let name:String
}
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
enum DogJSONErrors: String, Error {
case invalidNumberOfContainers
case noChildrenContainer
}
init(from decoder: Decoder) throws {
var containersArray = try decoder.unkeyedContainer()
guard containersArray.count == 2 else { throw DogJSONErrors.invalidNumberOfContainers}
let dogsContainer1 = try containersArray.decode([String:[String:[Dog]]].self)
let dogsContainer2 = try containersArray.decode([String:[String:[Dog]]].self)
guard let dogs1 = dogsContainer1["data"]?["children"], let dogs2 = dogsContainer2["data"]?["children"] else { throw DogJSONErrors.noChildrenContainer}
self.dogs1 = dogs1
self.dogs2 = dogs2
}
}
然后你可以像这样简单地解码一个 Result
实例:
do {
let dogResults = try JSONDecoder().decode(Result.self, from: dogsJSONString.data(using: .utf8)!)
print(dogResults.dogs1,dogResults.dogs2)
} catch {
print(error)
}
假设 JSON 看起来像这样:
[
{
"data": {
"children": [
{
"name": "Ralph"
},
{
"name": "Woofer"
}
]
}
},
{
"data": {
"children": [
{
"name": "Spot"
},
{
"name": "Trevor"
}
]
}
}
]
你有这个非常奇怪的结构,其中根项是一个数组,有两个对象,这两个对象中的每一个都是 Dog
个字典的数组。
但问题是Dog
数组是两个key in!您必须通过 data
和 children
才能到达。我
我希望结果(看起来很奇怪)是这样的,其中两个列表是分开维护的:
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
}
我知道我需要自定义 initializer/decoder,但我不确定如何访问它。
所以,简短的回答是:你不能,而长的回答更长。
tl;博士
蒙皮的一种方法是从结构的中间表示开始。像这样:
struct Intermediate: Codable {
struct Dog: Codable {
let name: String
}
struct Children: Codable {
let children: [Dog]
}
let data: Children
}
然后您可以将其转换为您的 Result
结构。您可以将 Result
结构转换为用于序列化的中间结构。这使您可以避免使用更复杂的配置键和编码器。如果你不想让任何人戳它,你可以在你的模块中保持中间表示私有。
使用中间结构进行垃圾回收并收集所需数据,然后处理它们。
因此,从在顶层声明的 Dog 结构开始:
struct Dog : Decodable { let name : String }
在您的实际代码中,制作临时本地结构来包装它并解码 JSON:
struct TheChildren : Decodable { let children : [Dog] }
struct TheData : Decodable { let data : TheChildren }
let arr = try! JSONDecoder().decode([TheData].self, from: yourJSONdata)
现在只需拉出想要的狗:
let dogs = arr.map {[=12=].data.children}
/*
[[Dog(name: "Ralph"), Dog(name: "Woofer")],
[Dog(name: "Spot"), Dog(name: "Trevor")]]
*/
那是狗数组的数组,所以两者 "arrays are maintained separately" 因为它们是结果数组的独立元素。这似乎是一个完全合理的表示。
现在,如果您想将该信息进一步填充到新结构中,没问题。它不会与您提出的结果结构相同,因为名称 dogs1
和 dogs2
没有出现在数据中,并且您不能在以下位置组成 属性 名称运行时(好吧,在 Swift 4.2 中你可以,但那是另一回事了)。但关键是,您已经轻松获得了 Dog 数据,并且没有额外的 material。并且没有真正的理由为什么访问名称 dogs1
下的第一个数组比通过索引获取它更好 dogs[0]
;事实上,后者实际上更好。以索引号结尾的 属性 名称 总是 难闻的气味,表明您真正需要的是某种集合。
您可以解码 JSON 而无需引入中间结构,同时通过将唯一键为 data
的外部 Dictionary
解码为嵌套 Dictionary
来保持类型安全属于 [String:[String:[Dog]]]
类型,这非常混乱,但可以工作,因为您在外部词典中只有 2 个嵌套层和单个键。
struct Dog: Codable {
let name:String
}
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
enum DogJSONErrors: String, Error {
case invalidNumberOfContainers
case noChildrenContainer
}
init(from decoder: Decoder) throws {
var containersArray = try decoder.unkeyedContainer()
guard containersArray.count == 2 else { throw DogJSONErrors.invalidNumberOfContainers}
let dogsContainer1 = try containersArray.decode([String:[String:[Dog]]].self)
let dogsContainer2 = try containersArray.decode([String:[String:[Dog]]].self)
guard let dogs1 = dogsContainer1["data"]?["children"], let dogs2 = dogsContainer2["data"]?["children"] else { throw DogJSONErrors.noChildrenContainer}
self.dogs1 = dogs1
self.dogs2 = dogs2
}
}
然后你可以像这样简单地解码一个 Result
实例:
do {
let dogResults = try JSONDecoder().decode(Result.self, from: dogsJSONString.data(using: .utf8)!)
print(dogResults.dogs1,dogResults.dogs2)
} catch {
print(error)
}