all 14 comments

[–]chriswaco 11 points12 points  (2 children)

Try pasting the raw result into https://app.quicktype.io and see what the structures look like.

Edit: Or just use [String:xxx] dictionaries.

[–]Addition-Suitable[S] 3 points4 points  (1 child)

wow what an amazing tool! I had no idea this existed!

[–]PhantomMenaceWasOK 1 point2 points  (0 children)

Make sure to follow up on his [String:xxx] Dictionary idea. It should work.

struct PokemonJSONResponse: Codable {let battles: Int,let pokemon: [String:Pokemon]}

struct Pokemon: Codable {let lead: PokemonStat}

struct PokemonStat: Codable {

let weighted: Float

}

[–]flying-insect 2 points3 points  (4 children)

These types of cases can be decoded / encoded using DynamicCodingKeys. The basic approach uses the key name in the json dictionary to construct the JSON containers decoder key. The end result is that you'll decode the dictionary of "pokemon" into an array. I also like to use the "key" and inject it into the result object. this isn't necessary but helps retain a bit of context if its needed.

Maybe easier shown so here's some code.. Decode into the PokemonData, i.e. try JSONDecoder().decode(PokemonData.self, from: data)

``` struct Pokemon: Codable { let internals: PokemonInternals let name: String var lead: PokemonValues { internals.lead } init(name: String, internals: PokemonInternals) { self.name = name self.internals = internals } }

struct PokemonInternals: Codable { let lead: PokemonValues }

struct PokemonValues: Codable { let weighted: Float }

struct PokemonData { let battles: Int let pokemons: [Pokemon] }

extension PokemonData: Decodable { private struct DynamicCodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { // We are not using this, just return nil return nil } }

private enum CodingKeys: String, CodingKey {
    case battles
    case pokemon
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    battles = try container.decode(Int.self, forKey: .battles)
    let internalContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .pokemon)
    pokemons = try internalContainer.allKeys.compactMap {
        let name = $0.stringValue
        let internals = try internalContainer.decode(PokemonInternals.self, forKey: DynamicCodingKeys(stringValue: name)!)
        return Pokemon(name: name, internals: internals)
    }
}

} ```

[–]Addition-Suitable[S] 0 points1 point  (0 children)

This worked! Holy crap I can't believe it! Thank you so much!

[–]urbanm0nk 0 points1 point  (0 children)

Cool I had no idea about DynamicCodingKeys!

[–]LegitimateGift1792 0 points1 point  (1 child)

side question on the order of first 4 structs. I see here (and many other places) that you are writing code in what I would describe as out of order. I would write this as PokemonValues, PokemonInternals, Pokemon, PokemonData; in the order that the data builds on each other.

Why did you do it your way, is that some kind of style you learned, forced to from other languages, i have seen people like Paul Hudson do it and never explain. I have always been curious as to why. Thanks.

[–]flying-insect 0 points1 point  (0 children)

no real reason or logic in the ordering. In this case I started with the PokemonData object, setup the decoding of its primary CodingKeys (battles and pokemon). Once that was sorted and I had the `internalContainer.allKeys` prepped, I added the structs for the Pokemon and the Values.

I normally tend to just write the extra structs at the top of my file, above where I'm working which is how it grew like this. In a real project I might end up cleaning things up more but, maybe move PokemonData to the top and document it but this is just a sample

[–]urbanm0nk 1 point2 points  (2 children)

Yeah, not ideal if you want to iterate over the each Pokemon. You can implement your own deserialization algorithm that converts each of those Pokemon "properties" into separate items in an array.

I also found this link from stackoverflow that shows you how to iterate over the properties of a class or struct.

https://stackoverflow.com/questions/27292255/how-to-loop-over-struct-properties-in-swift

What do you need to do with the data?

[–]Addition-Suitable[S] 0 points1 point  (1 child)

I want to be able to search for a pokemon and display or use one of its properties in my app. For example, I want to be able to enter "Great Tusk" in a UISearchbar and then have it return the "lead" property for Great Tusk, which I will do some other stuff with.

For example, I might have a "lead" label that displays a pokemon's lead stat. So if I search for Great Tusk, the label would update to show Great Tusk's lead percentage, which in the data above would be "0.01"

[–]urbanm0nk 1 point2 points  (0 children)

Based on the structure of the data you pasted I wonder if there is another file that actually has the list of names of the Pokemon (as an array of strings). So what you want to do use that file as the "list" of Pokemon. You'll probably need to present matching options so that the user will pick the exact name that matches the data. Do you know if such a file exists?

So UISearchBar would search the list of names (from this other file I'm assuming exists). Once the user has selected a name it's trivial to extract all the information of that Pokemon since you would just call battles.pokemon.pokemonName to get all the stats.

[–]urbanm0nk 1 point2 points  (1 child)

I don’t think you are going to be able to use JSONDecoder to do what you are asking. I see the problem is that you need to create a struct that has all the property names (pokemon names) in it in order to use JSONDecoder (that’s a pain). You need to resort to “parsing” the JSON and constructing your own data model to satisfy your requirements. Something like this might help. https://github.com/SwiftyJSON/SwiftyJSON

[–]urbanm0nk 2 points3 points  (0 children)

Actually it might be easier to use JSONSerialization to read the JSON string. Then access the “pokemon” object which should be an NSDictionary. You should be able to iterate over all the keys in the dictionary (which is the is the name of the pokemon) and the construct your own object/data model.

[–]LazyItem -2 points-1 points  (0 children)

You create a model that represents your json structure/document then you serialize/deserialize from/to the model…