1

The API website I used. I made an account -> Documentation -> Brawlers if you want to try it out

Here is part of the JSON I want to parse. I want to print the name inside the "starPowers" array as well as the "gadgets" array.

{
   "items":[
      {
         "id":16000014,
         "name":"BO",
         "starPowers":[
            {
               "id":23000090,
               "name":"CIRCLING EAGLE"
            },
            {
               "id":23000148,
               "name":"SNARE A BEAR"
            }
         ],
         "gadgets":[
            {
               "id":23000263,
               "name":"SUPER TOTEM"
            },
            {
               "id":23000289,
               "name":"TRIPWIRE"
            }
         ]
      }
   ]
}

I tried this first way of parsing the JSON which worked but I couldn't find a way to print the "name" inside the "starPower" or "gadgets" array.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let apiToken = "abcdefg"

        if let url = URL(string: "https://api.brawlstars.com/v1/brawlers") {
            
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
            request.addValue("Bearer \(apiToken)", forHTTPHeaderField: "authorization")
            request.addValue("application/json", forHTTPHeaderField: "Accept")

            URLSession.shared.dataTask(with: request) { (data, response, error) in
                if error != nil {
                    print(error!)
                } else {
                    guard let data = data else {return}

                    do {
                        let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as AnyObject
                        print(jsonResult)
                        if let items = jsonResult["items"] as? NSArray {
                            for item in items as [AnyObject] {
                                if let name = item["name"] {
                                    print(name!)
                                }
                                if let gadgets = item["gadgets"] {
                                    print(gadgets!)
                                }
                                if let starPowers = item["starPowers"]{
                                    print(starPowers!)
                                }
                            }
                        }
                 } catch {
                        print("JSON processing failed: \(error.localizedDescription)")
                    }
                }
            }.resume()
        } else {
            print("Something went wrong")
        }
    }
}

So I added another file with this data:

struct StarPowers: Codable {
    let starPowerName: String
}

struct Gadgets: Codable {
    let gadgetName: String
}

struct Items: Codable {
    let name: String
    let starPowers: [StarPowers]
    let gadgets: [Gadgets]
} 

And replaced the code inside the do statement with this but it returned "JSON processing failed: The data couldn’t be read because it is missing." (Catch statement)

let items = try JSONDecoder().decode(Items.self, from: data)
print(items.name.first ?? "")

I'm still fairly new to Swift in general as well as StackOverflow, so any help or feedback will be greatly appreciated. Thanks!

pawello2222
  • 46,897
  • 22
  • 145
  • 209

2 Answers2

1

When using the Codable APIs you want to model all the data you see. So this JSON:

{
   "items":[
      {
         "id":16000014,
         "name":"BO",
         "starPowers":[
            {
               "id":23000090,
               "name":"CIRCLING EAGLE"
            },
            {
               "id":23000148,
               "name":"SNARE A BEAR"
            }
         ],
         "gadgets":[
            {
               "id":23000263,
               "name":"SUPER TOTEM"
            },
            {
               "id":23000289,
               "name":"TRIPWIRE"
            }
         ]
      }
   ]
}

should result in these structs:

//This represents one star power
struct StarPower: Codable {
    let id: Int
    let name: String
}
//This represents one gadget
struct Gadget: Codable {
    let id: Int
    let name: String
}
//This represents one "item" (I think they're brawlers, but I didn't make an account so I can't confirm what the API calls them
struct Item: Codable {
    let id: Int
    let name: String
    let starPowers: [StarPower]
    let gadgets: [Gadget]
}

However, the excerpt that you provided is actually of type [String:[Item]] (a dictionary (aka a JSON object) with one string key, with the value being an array of items. You could make a Codable struct to handle that, or you can just do this:

let decoded = try! JSONDecoder().decode([String:[Item]].self, from: data)
let items = decoded["items"]!
//items is an array of type [Item]
//  Using your example, it would only have one element.
let element = items.first!
for starPower in element.starPowers {
    print(starPower.name)
}
for gadget in element.gadgets {
    print(gadget.name)
}

(keep in mind that I'm force-unwrapping and force-trying because I'm assuming that fetching the data worked fine and that it is correct. You should probably check these assumptions and use constructs such as if-let and do-catch)

Sam
  • 2,350
  • 1
  • 11
  • 22
0

Name the properties of your codable structs exactly as the keys in the corresponding JSON:

struct StarPower: Codable {
    let name: String
}

struct Gadget: Codable {
    let name: String
}

struct Item: Codable {
    let name: String
    let starPowers: [StarPower]
    let gadgets: [Gadget]
} 

The decoder couldn't find the data, because "gadgetName" and "starPowerName" are not part of the JSON you provided.

Another tip: use singular while naming structs, so one element of gadgets is a gadget. the decoder doesn't care how you name your structs, it only cares about the properties

mflknr
  • 130
  • 1
  • 7