My data looks like this:
"places": [
{
"id": 15,
"name": "København",
"typeId": 6,
"coordinates": {
"lat": "55.6760968",
"lng": "12.5683372"
},
"count": 2779
},
{
"id": 19,
"name": "København S",
"typeId": 3,
"coordinates": {
"lat": "55.6508754",
"lng": "12.5991891"
},
"count": 1168
}
]
I wish to avoid this:
struct Places: Decodable {
let places: [Place]
}
Which is suggested by QuickType.io: https://app.quicktype.io?share=j22hopuBnkuHZziOSvxG And instead just decode within "places" list. Such that this would work:
let places = try JSONDecoder().decode([Place].self, from: data)
Possible solutions that I've found so far:
The 'Scuffed' solution: https://stackoverflow.com/a/62403633/13481876
Create generic decodable array struct: https://swiftsenpai.com/swift/decode-dynamic-keys-json/
If you find yourself needing this multiple times, then you can build your own generic struct that decodes over whichever key it finds:
struct Nester<T: Decodable>: Decodable {
let elements: [T]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let key = container.allKeys.first {
elements = try container.decode([T].self, forKey: key)
} else {
// we run into an empty dictionary, let's signal this
throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
}
}
// A coding key that accepts whatever string value it is given
struct CodingKeys: CodingKey {
let stringValue: String
var intValue: Int? { nil }
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) { return nil }
}
}
And with this in hand, you can extend JSONDecoder
in order to get a nicer call site:
extension JSONDecoder {
func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
try decode(Nester<T>.self, from: data).elements
}
}
Then it's just a matter of calling the new overload:
let places = try JSONDecoder().decode(nested: [Place].self, from: data)
P.S. if you want, you can hide the complex struct within the extension, resulting in something like this:
extension JSONDecoder {
func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
try decode(Nester<T>.self, from: data).elements
}
private struct Nester<T: Decodable>: Decodable {
let elements: [T]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let key = container.allKeys.first {
elements = try container.decode([T].self, forKey: key)
} else {
throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
}
}
struct CodingKeys: CodingKey {
let stringValue: String
var intValue: Int? { nil }
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) { return nil }
}
}
}
The downside is that you'll not be able to reuse the struct if you want to extend other decoders besides the JSON one.