0

I have a JSON file that is formatted in the following way:

[
  {
    "QID": "B002",
    "Stash": "Basic",
    "Category": "Geography",
    "Question": "What is the highest mountain on earth?",
    "Answer": "Mt Everest"
  },
  {
    "QID": "B003",
    "Stash": "Basic",
    "Category": "General",
    "Question": "What is the gestation period for a pregnant elephant?",
    "Answer": "2 years"
  }
]

I'm trying to make a structure so that I can load all the questions in my JSON file into my quiz-app. So far from what I've researched from JSON and the new "Decodable" thing apple added I have my Swift code as follows (Note there's a failed attempt commented out):

var STASHES_SELECTED = ["BasicStash", "MediumStash", "HardStash"]

    struct TriviaQuestion: Decodable {
        let QID: String
        let Stash: String
        let Categoroy: String
        let Question: String
        let Answer: String
    }

    func loadQuestionStash()
    {
        /*
        for var i in STASHES_SELECTED
        {
         let url = Bundle.main.url(forResource: STASHES_SELECTED[i], withExtension: "JSON") //CANT GET THIS TO WORK!, SAYS CANNOT SUBSCRIPT TYPE [STRING] WITH INDEX TYPE 'STRING'
        }*/

        if let url = Bundle.main.url(forResource: "BasicStash", withExtension: "JSON")
        {
            let json = try? Data(contentsOf: url)

            let questions = try? JSONDecoder().decode(TriviaQuestion.self, from: json!)
            print (questions!) //FATAL ERROR, FOUND NIL

        }
    }

As you can see from the code comments, that currently gives me a fatal error "found nil while unwrapping". So I assume that the previous line JSONDecoder(). failed horribly.
I am not sure if I am doing this correctly as it's my firs time working with JSONs and I've just been pretty much cookie-cutter following tutorials and posts online. I'd really appreciate some help here. Also the .self after TriviaQuestion was added by the system (I think the problem might be somewhere in there)

dvd.Void
  • 339
  • 1
  • 5
  • 21
  • Don't silently ignore the thrown error with `try?` – use `try` in a do-catch block and print out the error; it'll tell you exactly what went wrong. – Hamish Nov 27 '17 at 11:50
  • Also note that it’s Swift convention for property names to be `lowerCamelCase`, not `UpperCamelCase`. If the keys in your JSON differ, you can define your own `CodingKeys`, compare e.g https://stackoverflow.com/q/44396500/2976878 – Hamish Nov 27 '17 at 11:51
  • Even after fixing the typo I still got an error so I followed your advise and got this: **THERE WAS AN ERROR!!** typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil)) – dvd.Void Nov 27 '17 at 13:07
  • 1
    Change `.decode(TriviaQuestion.self` to `.decode([TriviaQuestion].self` – matt Nov 27 '17 at 13:13

2 Answers2

2

As @Oxthor mentioned the typing error,

I just want to add that always use quicktype.io to create your data struct. You will avoid typos and save your time:

// To parse the JSON, add this file to your project and do:
//
//   let triviaQuestion = Array.from(json: jsonString)!

import Foundation


struct TriviaQuestion: Codable {
    let answer: String
    let category: String
    let qID: String
    let question: String
    let stash: String
}

// MARK: Top-level extensions -

extension Array where Element == TriviaQuestion {
    static func from(json: String, using encoding: String.Encoding = .utf8) -> [PurpleTriviaQuestion]? {
        guard let data = json.data(using: encoding) else { return nil }
        return from(data: data)
    }

    static func from(data: Data) -> [TriviaQuestion]? {
        let decoder = JSONDecoder()
        return try? decoder.decode([TriviaQuestion].self, from: data)
    }

    static func from(url urlString: String) -> [TriviaQuestion]? {
        guard let url = URL(string: urlString) else { return nil }
        guard let data = try? Data(contentsOf: url) else { return nil }
        return from(data: data)
    }

    var jsonData: Data? {
        let encoder = JSONEncoder()
        return try? encoder.encode(self)
    }

    var jsonString: String? {
        guard let data = self.jsonData else { return nil }
        return String(data: data, encoding: .utf8)
    }
}

// MARK: Codable extensions -

extension TriviaQuestion {
    enum CodingKeys: String, CodingKey {
        case answer = "Answer"
        case category = "Category"
        case qID = "QID"
        case question = "Question"
        case stash = "Stash"
    }
}
Umair M
  • 10,298
  • 6
  • 42
  • 74
  • This kind of tools are the reason aliens haven't invaded us yet ;) – Umair M Nov 27 '17 at 11:56
  • Wow, that looks amazingly clean and efficient. I just have a question. According to the top comment it says to use it to just add the file to my project and do let triviaQuestion = TriviaQuestion.from(json: jsonString!). However copy-pasting that line (and defining jsonString as the url for the json (I ASSUME). Still gives me an error "Type TriviaQuestion has no member "From" – dvd.Void Nov 27 '17 at 13:02
  • Sorry! this is probably a mistake from quicktype. Try `Array.from` it works for me – Umair M Nov 27 '17 at 13:12
  • thanks. How do I define jsonString though? I thought it was the jsonURL but that doesn't work. – dvd.Void Nov 27 '17 at 13:20
  • In your case, it is the file content as string. – Umair M Nov 27 '17 at 13:52
  • @UmairM I don't understand, what do you mean? How do I define it? do I just say jsonString = "BasicStash.JSON" or is it just jsonString = "BasicStash" ? – dvd.Void Nov 27 '17 at 14:47
  • it is the actual json content which in the file. Just like you posted at the start of the question. You have to read the file to get that and store it in variable – Umair M Nov 27 '17 at 14:50
  • I can't get it to work :( let jsonString = try? String(contentsOf: jsonUrl!) and then using loadedQuestions = Array.from(json: jsonString) or how should I define it? (that gives me error "Ambiguous use of from") – dvd.Void Nov 27 '17 at 15:02
  • You need to learn how to read a file in swift. There are many resources out there. Just google it. Only ask a question if there is a genuine problem with your code. – Umair M Nov 27 '17 at 15:07
1

You mistyped the category attribute in the TriviaQuestion struct. You have categoroy but it should be category.

Oxthor
  • 512
  • 4
  • 15