TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Dictionary vs Array with JSON Decoder

Forums > SwiftUI

Hi all, I've been working through the 100 Days of SwiftUI course and am trying to stretch my API legs as an extension of the Friendface project (Day 54).

I'm struggling when decoding a dictionary, rather than an array as we have in the Friendface project. The URL at the link (https://jsonplaceholder.typicode.com/todos/1) only has four items: { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }

The code doesn't generate any errors, but when I hit run I see the "Download failed" error printed. I suspect this is because I'm attempting to decode a [User].self array rather than a User.self dictionary, but making that update (and a similar update to the @State var) sets off a series of errors.

Any help appreciated, I've spent enough time on this to feel pretty dumb right now.


import SwiftUI

struct User: Identifiable, Codable {
    let id: Int
    let title: String
}

struct ContentView: View {
    @State private var users = [User]()

    var body: some View {
        NavigationView {
            List(users) { user in
                NavigationLink {
                    Text(user.title)
                } label: {
                    Text(user.title)
                }
            }
            .navigationTitle("Friendface")
            .task {
                await fetchUsers()
            }
        }
    }

    func fetchUsers() async {
        // Don't re-fetch data if we already have it
        guard users.isEmpty else { return }

        do {
            let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
            let (data, _) = try await URLSession.shared.data(from: url)

            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            users = try decoder.decode([User].self, from: data)
        } catch {
            print("Download failed")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

The code doesn't generate any errors, but when I hit run I see the "Download failed" error printed. I suspect this is because I'm attempting to decode a [User].self array rather than a User.self dictionary, but making that update (and a similar update to the @State var) sets off a series of errors.

That is exactly the problem. What errors are you seeing pop up as a result?

This works just fine:

import SwiftUI

struct User: Identifiable, Codable {
    let id: Int
    let title: String
}

struct DecodedUserView: View {
    @State private var user = User(id: 0, title: "")

    var body: some View {
        NavigationView {
            NavigationLink {
                Text(user.title)
            } label: {
                Text(user.title)
            }
            .navigationTitle("Friendface")
            .task {
                await fetchUsers()
            }
        }
    }

    func fetchUsers() async {
        do {
            let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
            let (data, _) = try await URLSession.shared.data(from: url)

            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            user = try decoder.decode(User.self, from: data)
        } catch {
            //give us some real error information
            print(error)
        }
    }
}

struct DecodedUserView_Previews: PreviewProvider {
    static var previews: some View {
        DecodedUserView()
    }
}

2      

Brilliant, thanks! Thanks for the quick code rehab.

The errors I was seeing on my code before when attempting my dictionary conversion... On the List code: "Initializer 'init(:rowContent:)' requires that 'User' conform to 'RandomAccessCollection'" On the Guard code: "Cannot convert value of type 'Binding<Subject>' to expected condition type 'Bool'" "Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'Binding<User>'" "Value of type 'User' has no dynamic member 'isEmpty' using key path from root type 'User'"

I suspect for the List error, List is incompatible with a dictionary as it's meant to serve up an array of data? Guard might be a similar story - it's meant to check if there's an empty array of data - but this is a dictionary which must not have the same "isEmpty" checker?

Last question for you - is it required to set initial values for the 'user' var, as you did in your @State above? It looked like for the Friendface project, Paul set his initial array as empty. When I try pulling those initial conditions out of my @State private var user (so, user = User()), Xcode complains it needs a value - fair. Then when I try "user: User" instead, the errors show up in the Previews section ("'ContentView' initializer is inaccessible due to 'private' protection level", "Missing argument for parameter 'user' in call").

2      

I suspect for the List error, List is incompatible with a dictionary as it's meant to serve up an array of data?

Yup. You don't have a Dictionary anyway, you have a single User item, which won't work with List either.

Guard might be a similar story - it's meant to check if there's an empty array of data - but this is a dictionary which must not have the same "isEmpty" checker?

Yes, the particular guard statement you were using was checking for an empty array but you didn't have an array. Dictionary does have an isEmpty property but, again, you don't have a Dictionary, you have a User.

is it required to set initial values for the 'user' var, as you did in your @State above? It looked like for the Friendface project, Paul set his initial array as empty. When I try pulling those initial conditions out of my @State private var user (so, user = User()), Xcode complains it needs a value - fair. Then when I try "user: User" instead, the errors show up in the Previews section ("'ContentView' initializer is inaccessible due to 'private' protection level", "Missing argument for parameter 'user' in call").

The way it's set up right now, user has to have a value. So we just set up one with the values of id = 0 and title = "".

You could change things around so that user held an optional User, which would allow you to leave it nil to start with, but that would require other changes as well to account for the possibility of a nil value.

Which way you choose to go is up to you.

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.