1

假设 JSON 看起来像这样:

[
    {
      "data": {
        "children": [
          {
            "name": "Ralph"
          },
          {
            "name": "Woofer"
          }
        ]
      }
    },
    {
      "data": {
        "children": [
          {
            "name": "Spot"
          },
          {
            "name": "Trevor"
          }
        ]
      }
    }
]

你有这个非常奇怪的结构,其中根项是一个数组,有两个对象,这两个对象中的每一个都是一个Dog字典数组。

但问题是Dog数组中有两个键!你必须经历datachildren到达它。我看到了这个答案,它描述了使用单个键深度进行操作,但是当它嵌套两个深度时,我似乎无法重现结果。

我希望结果(看起来很奇怪)是这样的,两个列表分别维护:

struct Result: Codable {
    let dogs1: [Dog]
    let dogs2: [Dog]
}

我知道我需要一个自定义初始化程序/解码器,但我非常不确定如何访问它。

4

3 回答 3

2

您可以解码该 JSON,而无需引入中间结构,同时通过解码Dictionary唯一键data为嵌套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)
}
于 2018-05-30T15:29:29.260 回答
1

所以,简短的回答是:你不能,长的答案更长。

tl;博士

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

对此进行蒙皮的一种方法是从结构的中间表示开始。像这样的东西:

struct Intermediate: Codable { struct Dog: Codable { let name: String } struct Children: Codable { let children: [Dog] } let data: Children }

然后您可以将其转换为您的Result结构。您可以将Result结构转换为中间结构以进行序列化。这可以让您摆脱对指令键和编码器的更复杂使用。如果你不想让任何人戳它,你可以在你的模块中保持中间表示的私有。

于 2018-05-30T15:17:28.307 回答
1

使用中间结构来进行转储并收集所需的数据,然后将其处理掉。

因此,从 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)

现在只需拉出所需的 Dogs:

let dogs = arr.map {$0.data.children}
/*
[[Dog(name: "Ralph"), Dog(name: "Woofer")], 
 [Dog(name: "Spot"), Dog(name: "Trevor")]]
*/

那是一个 Dogs 数组的数组,因此两个“数组是分开维护的”,因为它们是结果数组的单独元素。这似乎是一个完全合理的表示。

现在,如果您想将该信息进一步填充到新结构中,那很好。它不会与您设置的 Result 结构相同,因为名称dogs1dogs2不出现在数据中,并且您无法在运行时组成属性名称(好吧,在 Swift 4.2 中您可以,但那是另一个故事)。但重点是,您可以轻松获得 Dog 数据,而且无需额外材料。并且没有真正的理由为什么访问名称下的第一个数组dogs1比通过索引获取它更好dogs[0]; 确实,后者实际上更好。以索引号结尾的属性名称始终是一种不良气味,表明您真正需要的是某种集合。

于 2018-05-30T15:26:53.927 回答