I need to decode a dictionary like:
{
"Bob": "London",
"Alice": "Berlin"
...
}
Into an array of Person
structs:
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.
Q: What about
NSJsonSerialization
?A: I know it's quite trivial with
NSJsonSerialization
, but implementing it withDecodable
protocol was bugging me a bit :)
Some more thought reminded me I don't need AnyCodingKey in this case because this is such a simple structure. Decode as a [String: String]
, and then make the mapping:
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:
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:
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. This is not fixable with Foundation (JSONSerialization or JSONDecoder); you'd have to use a different JSON parser if you needed that.
In this case I'd prefer traditional JSONSerialization
, the returned dictionary can be easily mapped to an array of 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)
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.