0

I'm pretty sure my model is correct based on my data, I cannot figure out why I am getting the format error?

JSON:

{ "1596193200":{ "clientref":1, "type":"breakfast" }, "1596200400":{ "clientref":0, "type":"lunch" }, "1596218400":{ "clientref":2, "type":"dinner" } } 

model:

struct Call: Decodable { let clientref: Int? let type: String? } 

edit updated question with the code for decoding the json data from the URL:

class CallService { static let shared = CallService() let CALLS_URL = "url.com/Calls.json" func fetchCalls(completion: @escaping ([Call]) -> ()) { guard let url = URL(string: CALLS_URL) else { return } URLSession.shared.dataTask(with: url) { (data, response, error) in // handle error if let error = error { print("Failed to fetch data with error: ", error.localizedDescription) return } guard let data = data else {return} do { let call = try JSONDecoder().decode([Call].self, from: data) completion(call) } catch let error { print("Failed to create JSON with error: ", error.localizedDescription) } }.resume() } } 
6
  • tried Codable instead of Decodable ? Commented Jul 30, 2020 at 15:28
  • 2
    Show the code where you are decoding. I hope you did try decoder.decode([String: Call].self... But that's a guess. Commented Jul 30, 2020 at 15:29
  • Error is still the same with codable. I have added my decoding file to the question. Commented Jul 30, 2020 at 15:33
  • print("Failed to create JSON with error: ", error.localizedDescription) => ` print("Failed to create JSON with error: ", error), that's much better, and give the error. We can easily guess what's wrong, but it's important you learn where to look error, and doing it good. Also, if it's failing, do not hesitate to do: print("It failed with data stringified: \(String(data: data, encoding: .utf8)")) just after the error. Commented Jul 30, 2020 at 15:35
  • @Larme Thank you! Very helpful. I have now received the error "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)) Commented Jul 30, 2020 at 15:41

2 Answers 2

3

I strongly suggest to learn how to debug: it includes where to look, what info to get, where to get them, etc, and at the end, fix it.

That's a good thing that you print the error, most beginner don't.

print("Failed to create JSON with error: ", error.localizedDescription) 

=>

print("Failed to create JSON with error: ", error) 

You'll get a better idea.

Second, if it failed, print the data stringified. You're supposed to have JSON, that's right. But how often do I see question about that issue, when it fact, the answer wasn't JSON at all (the API never stated it will return JSON), the author were facing an error (custom 404, etc.) and did get a XML/HTML message error etc.

So, when the parsing fails, I suggest to do:

print("Failed with data: \(String(data: data, encoding: .utf8))") 

Check that the output is a valid JSON (plenty of online validators or apps that do that).

Now:

I'm pretty sure my model is correct based on my data,

Well, yes and no.

Little tip with Codable when debuting (and not using nested stuff): Do the reverse.

Make your struct Codable if it's not the case yet (I used Playgrounds)

struct Call: Codable { let clientref: Int? let type: String? } do { let calls: [Call] = [Call(clientref: 1, type: "breakfast"), Call(clientref: 0, type: "lunch"), Call(clientref: 2, type: "dinner")] let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted] let jsonData = try encoder.encode(calls) let jsonStringified = String(data: jsonData, encoding: .utf8) if let string = jsonStringified { print(string) } } catch { print("Got error: \(error)") } 

Output:

[ { "clientref" : 1, "type" : "breakfast" }, { "clientref" : 0, "type" : "lunch" }, { "clientref" : 2, "type" : "dinner" } ] 

It doesn't look like. I could only used an array to put various calls inside a single variable, and that's what you meant for decoding, because you wrote [Call].self, so you were expecting an array of Call. We are missing the "1596218400" parts. Wait, could it be a dictionary at top level? Yes. You can see the {} and the fact it uses "keys", not listing one after the others...

Wait, but now that we printed the full error, does it make more sense now?

typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)) 

Fix:

let dictionary = try JSONDecoder().decode([String: Call].self, from: data) completion(dictionary.values) //since I guess you only want the Call objects, not the keys with the numbers. 
Sign up to request clarification or add additional context in comments.

5 Comments

+1 Very good explanation. How often do we see questions starting with The Data Couldn't Be Read Because It Isn't in The Correct Format...
Too many times, that made me starting when not too lazy an article about debugging skills with all that kind of errors.
Extremely helpful explanation. The problem is solved and I've gained knowledge about debugging. Thank you very much!
If I wanted to get the key from each dictionary member, is that possible with this code? Could I do calls?.key ?
You have a dictionary, so you can do dictionary.values or dictionary.keys. It depends of what you want. You can also, return directly the dictionary.
1

From the code you provided it looks like you are trying to decode an Array<Call>, but in the JSON the data is formatted as a Dictionary<String: Call>.

You should try:

let call = try JsonDecoder().decode(Dictionary<String: Call>.self, from: data) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.