Can't parse local json file

Hi!
I am trying to parse a local JSON file but can't get it to work. The command print(title) is not being printed.

Here is my code:

func getJSONData() -> Data? {
    if let url = Bundle.main.url(forResource: "teste", withExtension: "json"),let data = try? Data(contentsOf: url) {
        return data
    } else {
        return nil
    }
}

init() {
    getData()
}

func getData() {
    guard let data = getJSONData() else { return }
    if let localData = try? JSONSerialization.jsonObject(with: data,options: []),let dictionary = localData as? [String:Any] {
        let id = dictionary["id"] as? Int ?? 1
        let title = dictionary["title"] as? String ?? ""
        let artist = dictionary["artist"] as? String ?? ""
        let isOut = dictionary["isOut"] as? String ?? ""
        let label = dictionary["label"] as? String ?? ""
        let vinylAudoID = dictionary["vinylAudoID"] as? String ?? ""
        let vinylCountry = dictionary["vinylCountry"] as? String ?? ""
        let vinylFormat = dictionary["vinylFormat"] as? String ?? ""
        let vinylID = dictionary["vinylID"] as? String ?? ""
        let vinylLocation = dictionary["vinylLocation"] as? String ?? ""
        let year = dictionary["year"] as? String ?? ""
        print(title)
    }
    
}

what am I missing?
thank you

You didn't show the json data itself (the meaningful beginning of the file if it's huge).

I'd also recommend using JSONDecoder instead of JSONSerialization (the latter still has its uses but it's rarely needed).

struct Song: Decodable { + Encodable (or just Codable) if you need to write those records
    var id: Int
    var title: String
    var artist: String? // use optional as needed
    ...
}

// change this structure accordingly
// you can use it with `JSONDecoder().decode(MyJson.self, from: data)`
//
struct MyJson: Decodable {
    var songs: [Song]
}

// depending upon your JSON it could be a different struct altogether, e.g. this:
typealias MyJson = [Song]    or 
typealias MyJson = [String: Song]
etc

Used in an SPM target? Or an app project?

SPM targets do not expose a Bundle if you do not declare resources on said target, and you cannot use Bundle.main but rather Bundle.module IIRC.

Where it's breaking for one thing. The possible points of failure are

  • the file cannot be found, in which case the first guard in getData() fails silently.
  • the deserialisation, in which case, the if silently skips the print()

Either run your code through a debugger to see why it fails or put a print statement in the guard's else clause and another after the if's closing brace. That way you'll be able to tell what broke.

My money is on the file not being found. If you are in a package or a library/framework, you need to make sure it is in the bundle for the main executable (or look in the framework/pckage bundle instead).

Thank you guys.I am trying to do this for a long time and after trying this other tutorial I changed my code to this one: It returns an error after falling on the catch of the parse function.

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding().onAppear {
if let localData = self.readLocalFile(forName: "vinyls") {
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 readLocalFile")
    }
    return nil
}

private func parse(jsonData: Data) {
    do {
        let decodedData = try JSONDecoder().decode(VinylModel.self, from: jsonData)
        print("Title : ", decodedData.title)
        print("Artist : ",decodedData.artist)
    } catch {
        print("ERROR Parse")
    }
}

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()
    }
}

}

the model file:
struct VinylModel: Codable {

let id: Int

let title: String

let artist: String

let isOut: String

let label: String

let vinylAudoID: String

let vinylCountry: String

let vinylFormat: String

let vinylID: String

let vinylLocation: String

let year: String

}

the json file:

[
{
"id" : 1,
"title" : "Iā€™m Missing You",
"artist" : "Fabrica",
"isOut" : "False",
"label" : "Dance Pool",
"vinylAudoID" : "00mwDSOTStNtIMRWIZPs",
"vinylCountry" : "Italy",
"vinylFormat" : "12",
"vinylID" : "DAN6652126",
"vinylLocation" : "LR25",
"year" : "1997"
},
{
"id" : 2,
"title" : "No More, Baby",
"artist" : "Disco Blu",
"isOut" : "False",
"label" : "DJ Approved",
"vinylAudoID" : "02Q6i5tJfUBNEXxanfMM",
"vinylCountry" : "Italy",
"vinylFormat" : "12",
"vinylID" : "APP 9706",
"vinylLocation" : "LR11",
"year" : "1997"
},
{
"id" : 3,
"title" : "Carramba",
"artist" : "Arena Blanca",
"isOut" : "False",
"label" : "Outta Records",
"vinylAudoID" : "02loclZIMqGHzIXI9Xo5",
"vinylCountry" : "Italy",
"vinylFormat" : "12",
"vinylID" : "OTA696004",
"vinylLocation" : "LR19",
"year" : "1996"
}
]

thank u

Your JSON has an array, so it should be

try JSONDecoder().decode([VinylModel].self, from: jsonData)

But I'd check first that the JSON data string is also okay before parsing.

2 Likes

You can see the error, either in debugger or just print it:

        print("ERROR Parse", error)

ERROR Parse typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: , debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

1 Like

Hi!
After changing line try JSONDecoder().decode(vinylModel.self,from: jsonData) to try JSONDecoder().decode([vinylModel].self,from: jsonData) like our friend Andreas66 told I got this error adding error variable to the print statement:
ERROR Parse valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 3529", intValue: 3529), CodingKeys(stringValue: "vinylAudoID", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
How do I handle empty entries on my JSON file?

Just make the fields optional if they may be absent / null in JSON:

let vinylAudoID: String?

note that both cases { "field": null } and absent "field" are treated the same way.
("but found null instead" can mean "but it is absent")

1 Like

Edit: tera got here before me but see example below.

let json = """
[
	{
		"id": 1,
		"name": "James Bond",
		"address": "Unknown"
	},
	{
		"id": 2,
		"name": "Ms Moneypenny"
	}
]
"""

struct Person: Codable {
	let id: Int
	let name: String
	let address: String?
}

if let data = json.data(using: .utf8) {
	let decoded = try JSONDecoder().decode([Person].self, from: data)
	for person in decoded {
		print("\(person.id): \(person.name) \(person.address ?? "No address")")
	}
}

Prints out:

1: James Bond Unknown
2: Ms Moneypenny No address
1 Like