7

I'm trying to implement the new Codable protocol, so I added Codable to my struct, but am stuck on decoding the JSON.

Here's what I had before:

Struct -

struct Question {
    var title: String
    var answer: Int
    var question: Int
}

Client -

...

guard let data = data else {
    return
}

do {
    self.jsonResponse = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
    let questionItems = self.jsonResponse?["themes"] as! [[String: Any]]

    questionItems.forEach {
        let item = Question(title: $0["title"] as! String,
                            answer: $0["answer"] as! Int,
                            question: $0["question"] as! Int)
        questionData.append(item)
    }

} catch {
    print("error")
}

Here's what I have now, except I can't figure out the decoder part:

Struct -

struct Question: Codable {
    var title: String
    var answer: Int
    var question: Int
}

Client -

...

let decoder = JSONDecoder()
if let questions = try? decoder.decode([Question].self, from: data) {
    // Can't get past this part
} else {
    print("Not working")
}

It prints "Not working" because I can't get past the decoder.decode part. Any ideas? Will post any extra code as needed, thanks!

EDIT:

Sample of API JSON:

{
  "themes": [
    {
      "answer": 1,
      "question": 44438222,
      "title": "How many letters are in the alphabet?"
    },
    {
      "answer": 0,
      "question": 44438489,
      "title": "This is a random question"
    }
  ]
 }

If I print self.jsonResponse I get this:

Optional(["themes": <__NSArrayI 0x6180002478f0>(
{
    "answer" = 7;
    "question" = 7674790;
    title = "This is the title of the question";
},
{
    "answer_" = 2;
    "question" = 23915741;
    title = "This is the title of the question";
}

My new code:

struct Theme: Codable {
    var themes : [Question]
}

struct Question: Codable {
    var title: String
    var answer: Int
    var question: Int
}

...

if let decoded = try? JSONDecoder().decode(Theme.self, from: data) {
    print("decoded:", decoded)
} else {
    print("Not working")
}
Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
SRMR
  • 3,064
  • 6
  • 31
  • 59
  • Print out the error and see what it says. – Kevin Jun 07 '17 at 22:17
  • 2
    Well it won't work with the data that decodes to `jsonResponse` in your first example, because that's a JSON object, not an array. You need to get the array from the `"themes"` key (define another `Codable` type that has this property). – Hamish Jun 07 '17 at 22:18
  • @Hamish yup thats kind of what I was thinking but wasn't totally sure yet, thanks! – SRMR Jun 08 '17 at 13:21

3 Answers3

8

If your JSON has a structure

{"themes" : [{"title": "Foo", "answer": 1, "question": 2},
             {"title": "Bar", "answer": 3, "question": 4}]}

you need an equivalent for the themes object. Add this struct

struct Theme : Codable {
    var themes : [Question]
}

Now you can decode the JSON:

if let decoded = try? JSONDecoder().decode(Theme.self, from: data) {
    print("decoded:", decoded)
} else {
    print("Not working")
}

The containing Question objects are decoded implicitly.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks! I think this is exactly the specificity I was looking for on the general idea of how I thought it might work was. Is the `if let decoded` supposed to be inside my `do` statement or outside of it? – SRMR Jun 08 '17 at 13:34
  • If you want to use `do - catch` you don't need `if` but the `decode` line must be in the `do` scope. – vadian Jun 08 '17 at 13:58
  • Perfect. I added my JSON to my original question, does that look like that you were thinking it did? I'm still getting "Not Working" when I run the new code, and I can't figure out what the problem is even though I know I'm so close to getting it right. And its hard for me to debug that `if let decoded` statement to figure it out. – SRMR Jun 08 '17 at 14:32
  • Did you add the `Theme` struct and change `decode(Question.self` to `decode(Theme.self`? – vadian Jun 08 '17 at 14:47
  • Yeah, I updated my original question with the code I'm using now. I'm trying to figure out why it's not working. Is it not mapping to the json "themes" without me specifying that somewhere in my code like I did before with `self.jsonResponse?["themes"]` or something? Trying to just wrap my head around what I'm missing. – SRMR Jun 08 '17 at 15:05
  • You have to pass the raw data without the `JSONSerialization` part. The received data must have exactly the structure as in my answer. Any additional property breaks the decoding. – vadian Jun 08 '17 at 15:11
  • So if there is a `title2` coming in from the API and I don't have a property in my struct for it, then it breaks? Trying to make sure I understand exactly what you are saying there, and what I have wrong in my code or what I might need to account for with the results of the API json I receive. – SRMR Jun 08 '17 at 15:16
  • 1
    Yes, the name (and number) of the properties must exactly match the keys in the JSON. If you want to have different property names you have to add an enum `CodingKeys : String, CodingKey {}` and map the cases (JSON keys) to their raw values (property names). For debugging you must not use `try?` wrap the line in a `do - catch` block and print the error in the `catch` clause. – vadian Jun 08 '17 at 15:21
  • What if there's more keys? i.e answer, question, title, length and 50 more. I just want to pull 2 out of 50. how do you write that? – masaldana2 Jun 13 '17 at 03:31
  • You can write a custom initializer to consider only specific keys by conforming to the `Decodable` protocol. – vadian Jun 13 '17 at 04:33
1

You're getting this error because your JSON is likely structured as so:

{
  "themes": [
    { "title": ..., "question": ..., "answer": ... },
    { "title": ..., "question": ..., "answer": ... },
    { ... }
  ],
  ...
}

However, the code you wrote expects a [Question] at the top level. What you need is a different top-level type that has a themes property which is a [Question]. When you decode that top-level type, your [Question] will be decoded for the themes key.

Itai Ferber
  • 28,308
  • 5
  • 77
  • 83
0

Hello @all I have added the code for JSON Encoding and Decoding for Swift 4.

Please use the link here

CrazyPro007
  • 1,006
  • 9
  • 15