Home > OS >  Retrieving data from nested JSON
Retrieving data from nested JSON

Time:12-15

I'm struggling understanding how to get data from my JSON file and using it (for instance on pressing a button, it would change label texts or descriptions), despite having read countless of threads I'm still stuck, but here's what I've tried to do from those articles and stackoverflow threads.

Say I have this type a json file named Animals.json :

{
"animal": [

    {
       "name":"Cat",
       "image" : "cat.jpg",
       "category": "mammal"
    },
   
    {
        "name":"Eagle",
        "image" : "eagle.jpg",
        "category": "Bird",
    }
 ]
}

I created a struct in a separate file called Animals.swift :

struct Animals: Codable {
var animal: [Animal]
}

Then an other file named Animal.swift :

struct Animal : Codable {
var name: String
var image: String
var category: String   
}

Here's the code in my MainVC :

import UIKit

class InstrumentsVC: UIViewController {
override func viewDidLoad() {
        super.viewDidLoad()
    
    if let localData = self.readLocalFile(forName: "Animals") {
        self.parse(jsonData: localData)
    }
        
    
}


private func readLocalFile(forName name: String) -> Data? {
    do {
        if let bundlePath = Bundle.main.path(forResource: name,
                                             ofType: "json"),
            let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
            return jsonData
        }
        
    } catch {
        print(error)
    }
    return nil
}

private func parse(jsonData: Data) {
    do {

        let decodedData = try JSONDecoder().decode(Animals.self,
                                                   from: jsonData)
        print("animal: ", decodedData.animal.first?.name ?? "")
        
    } catch {
        print("decode error")
    }
}

private func loadJson(fromURLString urlString: String,
                      completion: @escaping (Result<Data, Error>) -> Void) {
    if let url = URL(string: urlString) {
        let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
            if let error = error {
                completion(.failure(error))
            }
            
            if let data = data {
                completion(.success(data))
            }
        }
        urlSession.resume()
    }
}

@IBAction func nextCategory(_ sender: UIButton) {
    //I'll write a function that allows changing categories then loading an image that will update an outlet and labels
}

}

I haven't gotten yet to the part of calling the loadJson function as I'm getting a decode error printed, and as I'm new to JSON decoding/parsing I'd like to know what's wrong and how to fix that. The idea is when I'm gonna press a button it'll loop through the "category" part of my JSON (I still haven't written the function but that should not be a problem), then I have an other button that will trigger looping through "name" (this is a simplified JSON as there's gonna be plenty of elements from the same category ; it won't be animals either but some stuff related to music, I just thought it'd be simpler to understand like that with animals instead of putting some music theory stuff).

For now I'm stuck with this error printed that is caught in readLocalFile function ; then from the multiple articles I've read I apparently need to call the loadJson function but I'm not quite sure yet about how to do all of that in order to get the name/image/category of each element. Thanks

CodePudding user response:

What code you have shared is actually working. enter image description here

If you are stuck with a decode error. Probably the reason will be

  1. Any null value or any Struct (Animal) member is missing (name, image, category)

So you could change your struct member like optional. So if any fail case it can keep nil value. Make struct like this

struct Animals: Codable {
var animal: [Animal]?
}

struct Animal : Codable {
var name: String?
var image: String?
var category: String?
}

Don't forget to unwrap safely when accessing the value.

private func parse(jsonData: Data) {
    do {
        let decodedData = try JSONDecoder().decode(Animals.self, from: jsonData)
        print("animal: ", decodedData.animal?.first?.name ?? "")
    } catch {
        print("decode error")
    }
}

One more reason will be typecasting error. For eg, the member (name, image, category) is declared as a string in the struct but in the JSON if any of the values is an integer or other than String it will be a decode error. You should correct the json

CodePudding user response:

i wrote the full answer for you.. take this as reference.. good Luck.

import UIKit

struct Animal : Codable {
    var name: String
    var image: String
    var category: String
}

struct Animals: Codable {
    var animal: [Animal]
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let data = Data.readJsonFile(withName: "Animals")
        
        do {
            let animalList = try JSONDecoder().decode(Animals.self, from: data!)
            for item in animalList.animal {
                print(item.name)
            }
        } catch {
            print("error")
        }
    }
}



extension Data {
    static func readJsonFile(withName name: String) -> Data? {
        do {
            if let bundlePath = Bundle.main.path(forResource: name, ofType: "json"),
                let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
                return jsonData
            }
        } catch {
            print(error)
        }
        return nil
    }
}
  • Related