【问题标题】:How to extract data from nested JSON with Swift 4 with dynamic keys如何使用带有动态键的 Swift 4 从嵌套 JSON 中提取数据
【发布时间】:2017-11-08 08:19:36
【问题描述】:

我有一个使用上传时创建的唯一键的 JSON 数据结构。如果我逐行阅读每个字典项目,我可以阅读所有内容。但是,我正在尝试修改我的代码以使用 Swift 4 可编码属性。

不幸的是,阅读Ray Wenderlich tutorial 和阅读Ultimate Guide to JSON Parsing with Swift 并没有让我成为天才。

JSON 看起来像这个简单的示例: 请注意,“123”、“456”、“case1”、“case2”、“u1”、“u2”等键在运行时是未知的。

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

this SO question on flattening JSON 之后,我能够为我的大部分数据创建解码器,但不能为嵌套字典创建解码器(在示例中,案例就像嵌套字典一样)。我确信我错过了一些简单的东西。

如果我尝试包含注释掉的部分,playground 将不会运行,不会给出错误。

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

它适用于事物和用户,但我不得不忽略这些案例。在 cmets// 中查看 print 的输出。

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")

我尝试使用this SO question on decoding 的指导,这对我来说似乎更清晰,但我没有成功实施。

这个代码是also a GIST

我的问题有两个:

  1. 如何在我的事物中以嵌套数组的形式获取案例数据?
  2. 你能 建议一种更简洁/更短的编码方式?感觉就像我在 重复的事情,但我在 JSON 编码/解码的几个示例。

【问题讨论】:

  • 您目前无法通过Codable 协议在 Swift 中为 动态键 自动编码/解码值——这样的工作需要手动完成。
  • 我可以使用动态键来做到这一点 - 见上文。 “123”和“456”是动态键,拿到数据的时候不知道。它通过“PhantomKeys”工作 - 我想我是手动做的。但是,我仍然无法理解嵌套的“案例”,我认为这不是不可能的。
  • 在下面查看我的答案,我重用了您的“PhantomKeys”在您的“事物”中创建了一个可选的案例列表 - 我最初误解了您的问题,因此进行了编辑,我将原始回复留在那里只是为了参考

标签: json swift4


【解决方案1】:

你可以试试这样的:

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

编辑 我最初误解了您的问题,这是您为获取案例而改进的代码。这是一个快速的工作,所以可能不是最佳的,但你明白了:

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")

【讨论】:

  • 抱歉百万编辑,我花了一段时间才到达那里:)
  • 天哪。当然,我需要再次使用幻象键,因为案例具有未知的 id,就像事物和用户一样。感谢您解决问题@mike_t。
【解决方案2】:

您可以从当前的 json 中获取密钥

 jq -r 'keys[]'

在循环中按检索到的每个键查询之后

【讨论】:

  • 你能在swift中使用jq吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-18
  • 2021-06-10
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多