2

Pretty new to Swift and trying to learn SwiftUI this week. Attempting a GET request from a Joke API and eventually looking to get it to refresh every time you press a button.

I have a feeling I'm defining my structs incorrectly or maybe not decoding the returned data properly. I can print the string of data (as the json dictionary) but not access the values themselves through Swift.

import SwiftUI

struct Response : Codable {
    var joke: [Joke]
}

struct Joke : Codable {
    var setup: String
    var delivery: String
}

struct ContentView : View {
    @State private var joke = [Joke]()

    func loadData() {
        guard let url = URL(string: "https://sv443.net/jokeapi/v2/joke/Dark?type=twopart") else {
            print("Invalid URL")
            return
        }

        URLSession.shared.dataTask(with: url) { data, res, error in
            if let data = data {
                print("decoding...")
                let str = String(data: data, encoding: .utf8)
                print(str!)

                if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
                    print("decoded!")
                    // cannot get this to print
                    print(decodedResponse.joke)

                    // looks good, let's get out of here
                    return
                }
            }
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
        }.resume()
    }

    var body : some View {
        Text("test")
        .onAppear(perform: loadData)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
decoding...
{
    "category": "Dark",
    "type": "twopart",
    "setup": "When I was a kid, I made a really big sandcastle with my grandma.",
    "delivery": "Unfortunately that didn't impress anyone at the cremation...",
    "flags": {
        "nsfw": false,
        "religious": false,
        "political": false,
        "racist": false,
        "sexist": false
    },
    "id": 145,
    "error": false
}
Fetch failed: Unknown error

Probably just a dumb mistake I'm not catching but would love for some explanation or even some docs. The only docs/tutorials I've seen are showing how to work with specifically structured JSON responses. The one I'm pulling has a dictionary at root, so I guess that's where the discrepancy lies.

I assume I'd use @escaping for rendering in the UI?

Asperi
  • 123,447
  • 8
  • 131
  • 245
  • Don't use `try ?` - that will hide any decoding error. Use `do/try/catch` then you can catch any decoding error and print it to see what is going wrong. – Paulw11 Jun 11 '20 at 22:37

1 Answers1

2

The problem is that you're trying to decode an array:

struct Response : Codable {
    var joke: [Joke]
}

struct Joke : Codable {
    var setup: String
    var delivery: String
}

However the JSON response is just an object.

The solution may be to decode it as an object and not an array:

struct Response : Codable {
    var setup: String
    var delivery: String
}

Or, alternatively, change the decoding part to decode a Joke:

let decodedResponse = try? JSONDecoder().decode(Joke.self, from: data)
pawello2222
  • 23,992
  • 12
  • 59
  • 94
  • Doesn't his struct model also need to cover all fields of the JSON? – rs7 Jun 11 '20 at 22:27
  • 2
    @rs7 You can specify only the fields you need. The synthesised `init(from decoder:)` implementation will look only for `setup` and `delivery` in the Decoder. The other JSON fields will be ignored. – pawello2222 Jun 11 '20 at 22:37