简体   繁体   English

如何在 Swift 中以键为值将字典解码为结构体

[英]How to decode a dictionary into struct with key as a value in Swift

I need to decode a dictionary like:我需要解码一个字典,如:

{
  "Bob": "London",
  "Alice": "Berlin"
  ...
}

Into an array of Person structs:进入Person结构数组:

struct Person {
  let name: String
  let city: String
}

I would like to implement this mapping using Coding protocol, but struggling to use each key as a value for the struct.我想使用Coding协议来实现这个映射,但很难将每个键用作结构的值。

Q: What about NSJsonSerialization ?问: NSJsonSerialization怎么样?

A: I know it's quite trivial with NSJsonSerialization , but implementing it with Decodable protocol was bugging me a bit :)答:我知道NSJsonSerialization很简单,但是用Decodable协议实现它让我有点Decodable :)

Some more thought reminded me I don't need AnyCodingKey in this case because this is such a simple structure.更多的想法提醒我在这种情况下我不需要 AnyCodingKey 因为这是一个如此简单的结构。 Decode as a [String: String] , and then make the mapping:解码为[String: String] ,然后进行映射:

struct PersonList: Decodable {
    let persons: [Person]

init(from decoder: Decoder) throws {
    self.persons = try decoder.singleValueContainer()
        .decode([String: String].self)
        .map(Person.init)
    }
}

let list = try JSONDecoder().decode(PersonList.self, from: json).persons

// [Person(name: "Bob", city: "London"), Person(name: "Alice", city: "Berlin")]

Old answer, because sometimes this technique is handy.旧答案,因为有时这种技术很方便。

The key tool for this is the oft-needed CodingKey (that really should be in stdlib), AnyCodingKey:对此的关键工具是经常需要的 CodingKey(它确实应该在 stdlib 中),AnyCodingKey:

struct AnyCodingKey: CodingKey {
    var stringValue: String = ""
    init?(stringValue: String) { self.stringValue = stringValue }
    var intValue: Int?
    init?(intValue: Int) { self.intValue = intValue }
}

With that, you just need a container to hang the decoder on.有了这个,你只需要一个容器来挂解码器。 Call it PersonList:称之为 PersonList:

struct PersonList: Decodable {
    let persons: [Person]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: AnyCodingKey.self)
        self.persons = try container.allKeys.map { key in
            Person(name: key.stringValue,
                   city: try container.decode(String.self, forKey: key))
        }
    }
}

let list = try JSONDecoder().decode(PersonList.self, from: json).persons

// [Person(name: "Bob", city: "London"), Person(name: "Alice", city: "Berlin")]

This just maps each key/value to a Person.这只是将每个键/值映射到一个人。

Keep in mind that JSON objects are not ordered, so the resulting array may be in a different order than the JSON, as they are in this example.请记住,JSON 对象没有排序,因此结果数组的顺序可能与 JSON 不同,如本示例中所示。 This is not fixable with Foundation (JSONSerialization or JSONDecoder);这无法通过 Foundation(JSONSerialization 或 JSONDecoder)修复; you'd have to use a different JSON parser if you needed that.如果需要,您必须使用不同的 JSON 解析器。

In this case I'd prefer traditional JSONSerialization , the returned dictionary can be easily mapped to an array of Person 在这种情况下,我希望使用传统的JSONSerialization ,返回的字典可以轻松地映射到Person数组

let json = """
{
  "Bob": "London",
  "Alice": "Berlin"
}
"""

struct Person {
  let name: String
  let city: String
}

do {
    if let dataDictionary = try JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String:String] {
        let people = dataDictionary.map{Person(name: $0.key, city: $0.value)}
        print(people)
    }
} catch {
    print(error)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM