Here is the JSON I need to decode, a picture of it is available in the link below as well as the text (formatting wont work so it looks ugly). I am pretty sure I represented it correctly with my structs.
{ "success": { "total": 1 }, "contents": { "quotes": [ { "quote": "The last time doesn't exist. It's only this time. And everything is going to be different this time. There's only now.", "author": "Bill Murray", "length": "118", "tags": [ "inspire", "present" ], "category": "inspire", "title": "Inspiring Quote of the day", "date": "2019-01-16", "id": null } ], "copyright": "2017-19 theysaidso.com" } }
Whenever I run my code, which attempts to take the fields from the JSON and store them into variables in order to display them in a UITableView, it fails. I test it by trying to make a label appear as with the author's name as its title. "author" is a field in the JSON. These are the important parts of the code:
Class ViewController: UITableViewController {
...
var quoteArray = [Quote]()
//quoteArray Stores the quote objects that contain the fields I need
.....
//STRUCTS TO REPRESENT THE JSON
struct Quote: Decodable {
let quote: String?
let author: String?
let length: String?
let tags: [String]?
let category: String?
let title: String?
let date: String?
}
struct WebsiteObjectStruct: Decodable {
let success: SuccessStruct
let contents: ContentsStruct
}
struct SuccessStruct: Decodable{
let total: Int?
}
struct ContentsStruct: Decodable{
let quotes: [Quote]?
let copyright: String?
}
.....
//FUNCTION WHERE DECODING HAPPENS
fileprivate func fetchJSON(){
...
self.websiteObject = try decoder.decode(WebsiteObjectStruct.self, from: data)
self.tableView.reloadData()
...
}
...
//CELL FOR ROW AT FUNCTION FOR TABLEVIEW
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
let authorText = quoteArray[0].author
cell.textLabel?.text = author.text
//For quoteArray we are looking at zero index because in my JSON there
// is ONLY EVER ONE element, located at index 0, in quoteArray
return cell
}
}
The app runs and the the tableView is empty, it doesnt have the author's name (In this case bill murray). Anyway, here is the error message:
Failed to decode: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
It says it expected to decode an array but instead found a dictionary. Well I changed it one time to decode not an array but a struct, and had a variable declared in the class that was of type struct (the purpose of the struct mirrors the purpose of the array).
Short story is, I changed the code slightly to accomodate for the struct, and it could print the author's name to the console ONLY if the print statement was within the same coding brackets as the decoding statement. Still, it couldn't store it into a variable for use.
I don't think the problem is Array vs Dictionary but with the "underlyingError" the console talks about, that the Array is nil. No matter what type the variable is, whether Array or a Struct, the variable getting placed into the textField is always nil.
I get this error:
Fatal Error: unexpected found nil while unwrapping optional value
Perhaps an issue with threading or asynchrosity?
Update: this code works:
class MainNetworkManager{ //Request JSON Format from the wep API
static func fetchJSON(fetchUrl: String, quoteViewController: QuoteViewController) {
let urlString = fetchUrl
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
// link in description for video on JSONDecoder
let decoder = JSONDecoder()
// Swift 4.1
decoder.keyDecodingStrategy = .convertFromSnakeCase
//self.webStruct = try decoder.decode(WebsiteObjectStruct.self, from: data)
// self?.quoteArray = quotesArray
// self?.reloadInputViews()
let tempStruct = try decoder.decode(WebsiteObjectStruct.self, from: data)
//print(tempStruct.contents.quotes[0].length)
quoteViewController.webStruct = tempStruct
//quoteViewController.setupLabels(array: (tempStruct.contents.quotes))
quoteViewController.setupLabels(obj: tempStruct)
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}