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

SOLVED: Having issues decoding JSON, help needed

Forums > SwiftUI

Hi, I've been wrestling with this problem for a few hours and can't seem to find the issue (although I suspect it is with the JSON itself as I created the file).

I am trying to create a simple quiz type app for Fantasy Premier League players. I have created the list of questions as a JSON here...

[
{
        "id": 1,
        "question": "Budget midfielders and forwards (costing around £6.0m) tend to provide worse value than defenders at the same price",
        "category": "bravo"
    },

{
        "id": 2,
        "question": "The best way to spend transfers is on buying the best captain option for each gameweek",
        "category": "romeo"
    },

{
        "id": 3,
        "question": "I spend a lot of transfers trying to get the best budget midfielders and forwards (costing around £6.0m)",
        "category": "yankee"
    },

{
        "id": 4,
        "question": "I strive to be a very patient manager",
        "category": "bravo"
    },

{
        "id": 5,
        "question": "I rarely captain fixture-proof players, but prefer to captain flat-track bullies",
        "category": "romeo"
    },

{
        "id": 6,
        "question": "I like to stay close to the template of top managers",
        "category": "yankee"
    },

{
        "id": 7,
        "question": "I always pay a lot of attention to which players provide the highest points per million",
        "category": "bravo"
    },

{
        "id": 8,
        "question": "I am willing to own players who are not nailed if they are very explosive.",
        "category": "romeo"
    },

{
        "id": 9,
        "question": "I usually prefer to “set and forget” my premium midfielders and forwards",
        "category": "yankee"
    },

{
        "id": 10,
        "question": "I frequently play formations with more than three defenders",
        "category": "bravo"
    },

{
        "id": 11,
        "question": "I usually prefer not to captain the same player that the majority of managers are captaining",
        "category": "romeo"
    },

{
        "id": 12,
        "question": "I start the season with a flexible team where it is easy to get any emerging player into my team",
        "category": "yankee"
    },

{
        "id": 13,
        "question": "I spend a high percentage of my budget on defenders",
        "category": "bravo"
    },

{
        "id": 14,
        "question": "I am willing to take hits for moves that have high potential upside",
        "category": "romeo"
    },

{
        "id": 15,
        "question": "I am confident that if I make good transfers, my budget midfielders and forwards will outscore defenders at a similar price",
        "category": "yankee"
    },

{
        "id": 16,
        "question": "I like owning consistent players, even if most managers consider them boring",
        "category": "bravo"
    },

{
        "id": 17,
        "question": "I am not very concerned if I lose team value by transferring heavy hitters in and out of my team",
        "category": "romeo"
    },

{
        "id": 18,
        "question": "Players that start the season in my team are often nothing more than placeholders for the best emerging option",
        "category": "yankee"
    },

{
        "id": 19,
        "question": "I am willing to stubbornly ignore bandwagons for players I do not trust",
        "category": "bravo"
    },

{
        "id": 20,
        "question": "Taking big risks is essential to achieving my FPL goals",
        "category": "romeo"
    },

{
        "id": 21,
        "question": "I prefer to spread my risk by rarely doubling or tripling up on any team’s attack or defence",
        "category": "yankee"
    },

{
        "id": 22,
        "question": "I usually spread my budget fairly evenly around my team rather than owning more premium midfielders and forwards",
        "category": "bravo"
    },

{
        "id": 23,
        "question": "I often like to own three attacking assets from the same team",
        "category": "romeo"
    },

{
        "id": 24,
        "question": "If there is a clear consensus among top managers on the best captain choice, it is a bad time to choose a different captain",
        "category": "yankee"
    },

{
        "id": 25,
        "question": "I am comfortable owning only one or two captain options at a time",
        "category": "bravo"
    },

{
        "id": 26,
        "question": "I dislike “coverage” picks and don’t mind being without an attacking asset from one top team to spend more money on a different team",
        "category": "romeo"
    },

{
        "id": 27,
        "question": "To the extent possible, I like to own all the highly owned premium midfielders and forwards",
        "category": "yankee"
    },

{
        "id": 28,
        "question": "I try to focus my transfers on removing the weakest link, more than on the players I’m getting in",
        "category": "bravo"
    },

{
        "id": 29,
        "question": "Spending transfers on defenders is usually wasteful",
        "category": "romeo"
    },

{
        "id": 30,
        "question": "It’s a myth that you need differentials to climb the ranks",
        "category": "yankee"
    }
]

When I try to decode using this code..

extension Bundle {
    func decode<T: Codable>(_ file: String) -> T {
        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(T.self, from: data) else {
            fatalError("Failed to decode \(file) from bundle.")
        }

        return loaded
    }
}

I get the error "Fatal error: Failed to decode Questions.json from bundle".

Is anyone able to assist in what may be the issue please? Thanks in advance

2      

hi,

you'll need to show how you call the extension on Bundle to load the JSON. you probably are using the wrong type there, because the JSON looks OK if you're using a struct like this:

struct MyType: Codable {
    let id: Int
    let question: String
    let category: String
}

hope that helps,

DMG

2      

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

Don't do this, use proper error handling. That try? turns any error that occurs into a nil and so hides the error from you.

Do something like this instead:

do {
    let loaded = decoder.decode(T.self, from: data)
    return loaded
} catch {
    print(error)
    fatalError("Failed to decode \(file) from bundle.")
}

Then you can see what your error is.

2      

I had to add try to the do catch block to get it to work in mine

do {
            let loaded = try decoder.decode(T.self, from: data)
            return loaded
        } catch {
            print(error)
            fatalError("Failed to decode \(file) from bundle.")
        }

2      

Whoops, yeah, that's required. Must have deleted too much when I got rid of the ?

2      

Hi Paul uses this as abridge version so @roosterboy correct in saying that you should use do, try, catch. This is Paul full version and I save it as a code snippet so when he does this I will just use this full version.

extension Bundle {
    func decode<T: Decodable>(
        _ type: T.Type,
        from file: String,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys
    ) -> T {
        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()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        // swiftlint:disable line_length
        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' - \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch - \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value = \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON.")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

You need to make sure that your Model is correct I use Ducky to make sure

struct Question: Codable, Identifiable {
  let id: Int
  let question: String
  let category: String
}

Then when you call from JSON use this

let questions = Bundle.main.decode([Question].self, from: "JSONname.json")

PS This also allow to change dateDecodingStrategy and keyDecodingStrategy from call if the JSON requires it.

let questions = Bundle.main.decode([Question].self, from: "JSONname.json", dateDecodingStrategy: .iso8601, keyDecodingStrategy: .convertFromSnakeCase)

3      

Thank you, I replaced my code for yours @NigelGee and it is now working.

I think the cause of the issue was that I was using

let questions : [String: Question] =
    Bundle.main.decode("Questions.json")

But replacing with

let questions = Bundle.main.decode([Question].self, from: "Questions.json")

Has resolved.

Thanks for your help

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.