Reputation: 29326
Say the JSON looks like this:
[
{
"data": {
"children": [
{
"name": "Ralph"
},
{
"name": "Woofer"
}
]
}
},
{
"data": {
"children": [
{
"name": "Spot"
},
{
"name": "Trevor"
}
]
}
}
]
Where you have this very weird structure where there the root item is an array, with two objects, and each of those two objects is an array of Dog
dictionaries.
But the problem is that the Dog
array is two keys in! You have to go through data
and children
to get to it. I saw this answer that depicts doing it with a single key deep, but I can't seem to reproduce the result when it's nested two deep.
I want the result to be (as weird as it seems) something like this, where both lists are maintained separately:
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
}
I know I need a custom initializer/decoder, but I'm very unsure of how to access it.
Upvotes: 1
Views: 1144
Reputation: 535556
Use intermediate structs to dumpster-dive and collect the desired data, and then dispose of them.
So, start with the Dog struct, declared at top level:
struct Dog : Decodable { let name : String }
In your actual code, make temporary local structs to wrap it and decode the JSON:
struct TheChildren : Decodable { let children : [Dog] }
struct TheData : Decodable { let data : TheChildren }
let arr = try! JSONDecoder().decode([TheData].self, from: yourJSONdata)
Now just pull out the desired Dogs:
let dogs = arr.map {$0.data.children}
/*
[[Dog(name: "Ralph"), Dog(name: "Woofer")],
[Dog(name: "Spot"), Dog(name: "Trevor")]]
*/
That's an array of arrays of Dogs, so both "arrays are maintained separately" in that they are separate elements of the result array. That seems a perfectly reasonable representation.
Now, if you want to further stuff that info into a new struct, fine. It isn't going to be the same as your posited Result struct, because the names dogs1
and dogs2
appear nowhere in the data, and you can't make up a property name at runtime (well, in Swift 4.2 you sort of can, but that's another story). But the point is, you've got the Dog data, easily, and with no extra material. And there's no real reason why accessing the first array under a name dogs1
is better than getting it by index as dogs[0]
; indeed, the latter is actually better. Ending a property name with an index number is always a Bad Smell suggesting that what you really needed is a collection of some sort.
Upvotes: 1
Reputation: 54745
You can decode that JSON without having to introduce intermediate structs while keeping type safety by decoding the outer Dictionary
whose only key is data
as a nested Dictionary
of type [String:[String:[Dog]]]
, which is pretty messy, but works since you only have 2 nested layers and single keys in the outer dictionaries.
struct Dog: Codable {
let name:String
}
struct Result: Codable {
let dogs1: [Dog]
let dogs2: [Dog]
enum DogJSONErrors: String, Error {
case invalidNumberOfContainers
case noChildrenContainer
}
init(from decoder: Decoder) throws {
var containersArray = try decoder.unkeyedContainer()
guard containersArray.count == 2 else { throw DogJSONErrors.invalidNumberOfContainers}
let dogsContainer1 = try containersArray.decode([String:[String:[Dog]]].self)
let dogsContainer2 = try containersArray.decode([String:[String:[Dog]]].self)
guard let dogs1 = dogsContainer1["data"]?["children"], let dogs2 = dogsContainer2["data"]?["children"] else { throw DogJSONErrors.noChildrenContainer}
self.dogs1 = dogs1
self.dogs2 = dogs2
}
}
Then you can simply decode a Result
instance like this:
do {
let dogResults = try JSONDecoder().decode(Result.self, from: dogsJSONString.data(using: .utf8)!)
print(dogResults.dogs1,dogResults.dogs2)
} catch {
print(error)
}
Upvotes: 2
Reputation: 6631
So, the short answer is: You can't and the long answer is longer.
tl;dr
One way to skin this is by starting with an intermediate representation of your structures. Something like this:
struct Intermediate: Codable {
struct Dog: Codable {
let name: String
}
struct Children: Codable {
let children: [Dog]
}
let data: Children
}
and then you can transform that into your Result
struct. And you can transform your Result
struct into an intermediate one for serialization. That lets you escape more complicated use of conding keys and encoders. You can keep the intermediate representations private in your module if you don't want anyone to poke at it.
Upvotes: 1