如何使用带有动态键的Swift 4从嵌套JSON中提取数据 [英] How to extract data from nested JSON with Swift 4 with dynamic keys
问题描述
我有一个JSON数据结构,该结构使用在上载时创建的唯一键.如果逐行阅读每个词典项目,那么我可以阅读所有内容.但是,我正在尝试修改代码以使用Swift 4编码属性.
I have a JSON data structure using unique keys that are created on upload. I can read all of it if I read each dictionary item line by line. However, I'm trying to modify my code to use the Swift 4 codable properties.
进行 Ray Wenderlich教程并阅读使用Swift进行JSON解析的最终指南不幸的是,这并没有推动我成为天才.
Doing the Ray Wenderlich tutorial and reading the Ultimate Guide to JSON Parsing with Swift unfortunately did not propel me to genius status.
JSON看起来像这个简单的例子: 注意,在运行时不知道诸如"123","456","case1","case2","u1","u2"之类的键.
The JSON looks like this simple example: NOTE that keys, like "123", "456", "case1", "case2", "u1", "u2" are not known at run time.
let json = """
{
"things" : {
"123" : {
"name" : "Party",
"owner" : "Bob",
"isActive" : true,
"cases" : {
"case1" : {
"no" : 1
},
"case2" : {
"no" : 2
}
}
},
"456" : {
"name" : "Bus",
"owner" : "Joe",
"isActive" : true
}
},
"users" : {
"u1" : {
"name" : "Summer"
},
"u2" : {
"name" : "Daffy"
}
}
}
"""
遵循关于JSON展平的SO问题 ,我能够为我的大部分数据创建一个解码器,但不能为嵌套的字典创建解码器(在本例中,案例的行为就像是一个嵌套的字典).我确信我缺少一些简单的东西.
Following this SO question on flattening JSON, I was able to create a decoder for most of my data, but not for the nested dictionaries (in the example, cases is acting like a nested dictionary). I am sure that I am missing something simple.
如果我尝试添加注释掉的部分,则游乐场将无法运行,不会出现错误.
If I attempt to include the commented out portion, the playground will not run, no error is given.
struct Thing: Decodable {
let id: String
let isActive: Bool
let name: String
let owner: String
//var cases = [Case]()
init(id: String, isActive: Bool, name: String, owner: String){//}, cases: [Case]?) {
self.id = id
self.isActive = isActive
self.name = name
self.owner = owner
//self.cases = cases ?? [Case(id: "none", caseNumber: 0)]
}
}
struct User: Decodable {
let id: String
let name: String
}
struct Case: Decodable {
let id: String
let caseNumber: Int
}
struct ResponseData: Decodable {
var things = [Thing]()
var users = [User]()
enum CodingKeys: String, CodingKey {
case trips
case users
}
private struct PhantomKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
}
private enum ThingKeys: String, CodingKey {
case isActive, name, owner, cases
}
private enum UserKeys: String, CodingKey {
case name
}
private enum CaseKeys: String, CodingKey {
case id
case caseNumber = "no"
}
init(from decoder: Decoder) throws {
let outer = try decoder.container(keyedBy: CodingKeys.self)
let thingcontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .things)
for key in thingcontainer.allKeys {
let aux = try thingcontainer.nestedContainer(keyedBy: ThingKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let owner = try aux.decode(String.self, forKey: .owner)
let isActive = try aux.decode(Bool.self, forKey: .isActive)
// let c = try aux.nestedContainer(keyedBy: CaseKeys.self, forKey: .cases)
// var cases = [Case]()
// for ckey in c.allKeys {
// let caseNumber = try c.decode(Int.self, forKey: .caseNumber)
// let thiscase = Case(id: ckey.stringValue, caseNumber: caseNumber)
// cases.append(thiscase)
// }
let thing = Thing(id: key.stringValue, isActive: isActive, name: name, owner: owner)//, cases: cases)
things.append(thing)
}
let usercontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .users)
for key in usercontainer.allKeys {
let aux = try usercontainer.nestedContainer(keyedBy: UserKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let user = User(id: key.stringValue,name: name)
users.append(user)
}
}
}
它适用于事物和用户,但是我不得不忽略这种情况.请参阅注释//中的打印输出.
It works for the things and users, but I have to ignore the cases. See the output of print in comments//.
let data = json.data(using: .utf8)!
let things = try JSONDecoder().decode(ResponseData.self, from: data).things
print(things[0])
//Thing(id: "456", isActive: true, name: "Bus", owner: "Joe")
let users = try JSONDecoder().decode(ResponseData.self, from: data).users
print(users[0])
//User(id: "u1", name: "Summer")
我尝试使用这个关于解码的问题对我来说似乎干净得多,但我尚未成功实现.
I have tried to use the guidance from this SO question on decoding that seems much cleaner to me, but I have not successfully implemented it.
此代码为也是GIST
我的问题是双重的:
- 如何在我的Thing中以嵌套数组的形式获取Case数据?
- 可以吗 建议更干净/更短的方式对此进行编码?感觉就像我 重复一遍,但是我已经看到了这种包装器结构 JSON编码/解码的几个示例.
- How can I get the Case data as a nested array in my Thing?
- Can you suggest a cleaner/shorter way to code this? It feels like I'm repeating things, but I have seen this kind of wrapper structure in several examples for JSON encoding/decoding.
推荐答案
您可以尝试执行以下操作:
You can try something like this:
let data = jsonData.data(using: .utf8)
let json = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let things = json["things"] as! [String:Any]
for (thing_key, thing_value) in things as [String:Any] {
let thing = thing_value as! [String:Any]
if let cases = thing["cases"] as? [String:Any]{
for (case_key, case_value) in cases {
print(case_key)
print(case_value)
}
}
}
编辑 我最初误解了您的问题,这是您为获得案例而改进的代码.这是一项快速的工作,因此可能不是最佳选择,但您明白了:
EDIT I initially missunderstood your question , here is your code improved to obtain the cases. It was a quick job so might not be optimal, but you get the idea:
struct Thing: Decodable {
let id: String
let isActive: Bool
let name: String
let owner: String
var cases: [Case]?
init(id: String, isActive: Bool, name: String, owner: String , cases: [Case]?) {
self.id = id
self.isActive = isActive
self.name = name
self.owner = owner
self.cases = cases
}
}
struct User: Decodable {
let id: String
let name: String
}
struct Case: Decodable {
let id: String
let caseNumber: Int
}
struct ResponseData: Decodable {
var things = [Thing]()
var users = [User]()
enum CodingKeys: String, CodingKey {
case things
case users
case cases
}
private struct PhantomKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
}
private enum ThingKeys: String, CodingKey {
case isActive, name, owner, cases
}
private enum UserKeys: String, CodingKey {
case name
}
private enum CaseKeys: String, CodingKey {
case no
}
init(from decoder: Decoder) throws {
let outer = try decoder.container(keyedBy: CodingKeys.self)
let thingcontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .things)
for key in thingcontainer.allKeys {
let aux = try thingcontainer.nestedContainer(keyedBy: ThingKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let owner = try aux.decode(String.self, forKey: .owner)
let isActive = try aux.decode(Bool.self, forKey: .isActive)
var cases:[Case]? = []
do{
let casescontainer = try aux.nestedContainer(keyedBy: PhantomKeys.self, forKey: .cases)
for case_key in casescontainer.allKeys{
let caseaux = try casescontainer.nestedContainer(keyedBy: CaseKeys.self, forKey: case_key)
let no = try caseaux.decode(Int.self, forKey: .no)
let thingCase = Case(id:case_key.stringValue, caseNumber: no)
cases?.append(thingCase)
}
}catch{ }
let thing = Thing(id: key.stringValue, isActive: isActive, name: name, owner: owner , cases: cases)
things.append(thing)
}
let usercontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .users)
for key in usercontainer.allKeys {
let aux = try usercontainer.nestedContainer(keyedBy: UserKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let user = User(id: key.stringValue,name: name)
users.append(user)
}
}
}
这将产生以下输出:
let data = json.data(using: .utf8)!
let things = try JSONDecoder().decode(ResponseData.self, from: data).things
print("-----")
for thing in things{
print(thing)
}
print("---")
let users = try JSONDecoder().decode(ResponseData.self, from: data).users
for user in users{
print(user)
}
-----
Thing(id: "456", isActive: true, name: "Bus", owner: "Joe", cases: Optional([]))
Thing(id: "123", isActive: true, name: "Party", owner: "Bob", cases: Optional([__lldb_expr_283.Case(id: "case1", caseNumber: 1), __lldb_expr_283.Case(id: "case2", caseNumber: 2)]))
---
User(id: "u1", name: "Summer")
User(id: "u2", name: "Daffy")
这篇关于如何使用带有动态键的Swift 4从嵌套JSON中提取数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!