UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

JSON Import | Array not Dictonary

Forums > SwiftUI

I am attempting to load race data into my application, and I'm using Loading a specific kind of Codable data as my guide.

I've taken a CSV and converted it to JSON using an online tool.

Here's my struct for holding each individual race result:

struct IndividualResult: Codable, Identifiable {
    let place: Int
    let name: String
    let city: String
    let id: Int
    let age: Int
    let agePlace: Int
    let chipTime: String
    let gunTime: String
    let totalPace: String
}

And my bundle extension to load and decode the JSON. I've modified this to return a simple array of IndividualResult, instead of a dictonary in the example. I wonder if this is part of my problem.

extension Bundle {
    func decode(_ file: String) -> [IndividualResult] {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        guard let loaded = try? decoder.decode([IndividualResult].self, from: data) else {
            fatalError("Failed to decode \(file) from bundle.")
        }

        return loaded
    }
}

Here's my JSON: https://pastebin.com/QXngvmbS

When I run my application and try to load the JSON, I get this error: Fatal error: Failed to decode results.json from bundle.

I've compared my JSON to my struct and I think they match. Am I misunderstanding that my JSON represents and array? Could the '/' character in the totalPlace string cause an issue?

2      

Change this:

guard let loaded = try? decoder.decode([IndividualResult].self, from: data) else {
    fatalError("Failed to decode \(file) from bundle.")
}

return loaded

to this:

do {
    let loaded = try decoder.decode([IndividualResult].self, from: data)
    return loaded
} catch {
    print(error)
    fatalError()
}

and see what the actual error you are getting back is.

2      

Ah ha! That made finding the error easier!

valueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 118", intValue: 118), CodingKeys(stringValue: "age", intValue: nil)], debugDescription: "Expected Int value but found null instead.", underlyingError: nil))
raceresults/Bundle-Decodable.swift:27: Fatal error
2023-03-13 14:25:03.305494-0700 raceresults[5396:17657257] raceresults/Bundle-Decodable.swift:27: Fatal error

The 119th object has null as it's value.

 {
   "place": 119,
   "name": "Unknown Partic. 3120",
   "city": "",
   "id": 3120,
   "age": null,
   "agePlace": 1,
   "chipTime": "1:02:26",
   "gunTime": "1:02:26",
   "totalPace": "12:35/M"
 },

What's the best strategy to deal with these errors? I'd like to handle it gracefully if possible, as I won't be able to guarantee the data is clean. (Thought I may just clean it up to move on as this is a test/demo and not a real app.)

2      

Hi, I was able to get a result using the code below:

// MARK: - ResultElement
struct ResultElement: Codable {
    let place: Int
    let name: String
    let city: String
    let id: Int
    let age: Int?
    let agePlace: Int
    let chipTime: String
    let gunTime: String
    let totalPace: String
}

typealias Result = [ResultElement]
extension Bundle {
    func decode(_ file: String) -> Result {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        guard let loaded = try? decoder.decode(Result.self, from: data) else {
            fatalError("Failed to decode \(file) from bundle.")
        }

        return loaded
    }
}

I think is the optional in the age property because there is a racer who's age is null.

2      

typealias Result = [ResultElement]

Not a good idea to call this Result, since there is a built-in Result type that you will conflict with by doing so.

Since this is an array (i.e., more than one) of ResultElements, it should be called Results instead.

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.