Type 'User' does not conform to protocol 'Decodable'

hi, so I have 3 files in my xcode:

ContentView

import SwiftUI

struct ContentView: View {
    @State private var email = ""
    @State private var password = ""
    @State private var wrongEmail = 0
    @State private var wrongPassword = 0
    @State private var showingLoginScreen = false
    @State private var userData: User? // Declare a @State property for UserData

    
    var body: some View {
        NavigationView {
            ZStack {
                Color.blue
                    .ignoresSafeArea()
                Circle()
                    .scale(1.7)
                    .foregroundColor(.white.opacity(0.15))
                Circle()
                    .scale(1.35)
                    .foregroundColor(.white)
                VStack {
                    Text("Login")
                        .font(.largeTitle)
                        .bold().padding()
                    TextField("Email", text: $email)
                        .padding()
                        .frame(width: 300, height: 50)
                        .background(Color.black.opacity(0.05))
                        .cornerRadius(10)
                        .border(.red, width: CGFloat(wrongEmail))
                    SecureField("Password", text: $password)
                        .padding()
                        .frame(width: 300, height: 50)
                        .background(Color.black.opacity(0.05))
                        .cornerRadius(10)
                        .border(.red, width: CGFloat(wrongPassword))
                    Button("Login") {
                        authenticateUser(email: email, password: password)
                    }
                    .foregroundColor(.white)
                    .frame(width: 300, height: 50)
                    .background(Color.blue)
                    .cornerRadius(10)
                    
                    NavigationLink(
                                destination: ArtworkView(user: userData),
                                isActive: $showingLoginScreen
                            ) {
                                EmptyView()
                            }
//                    NavigationLink(destination: Text("You are logged in @\(email)"), isActive: $showingLoginScreen) {
//                        EmptyView()
//                    }
                }
            }.navigationBarHidden(true)
        }
    }
    func authenticateUser(email: String, password: String) {
        guard  let url = URL(string: "https://api.hangn.art/api/login") else {
            return
        }
        
        print("making api call")
        
        var request = URLRequest(url: url)
        // method, body, headers
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        let body: [String: AnyHashable] = [
            "email": email,
            "password": password
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
        //Make the request
        let task = URLSession.shared.dataTask(with: request) { data, _, error in
            guard let data = data, error == nil else {
                return
            }
            print(String(data: data, encoding: .utf8))
            let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase // Configure snake case decoding
            do {
//                let response = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
                let response = try decoder.decode(LoginResponse.self, from: data)
                            self.userData = response.user
                let userData = try decoder.decode(User.self, from: data)
                print("SUCCESS: \(userData)")
                wrongEmail = 0
                wrongPassword = 0
                showingLoginScreen = true
            }
            catch {
                print("Error parsing response data: \(error)")
                    print("Response data: \(String(data: data, encoding: .utf8) ?? "")")
            }
        }
        task.resume()
    }
}

//struct Response: Codable {
//    let email: String
//    let password: String
//}

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

ArtworkView

import SwiftUI

struct ArtworkView: View {
    var user: User?

        var body: some View {
            if let user = user {
                VStack {
                    Text("Name: \(user.firstName) \(user.lastName)")
                    Text("Email: \(user.email)")
                    Text("Description: \(user.description)")
                    // ... display other properties ...
                }
            } else {
                Text("Loading...")
            }
        }
}

User

import Foundation

struct LoginResponse: Decodable {
    let token: String
    let user: User
}

struct User: Decodable {
    let clientId: Int
    let createdAt: String
    let description: String
    let email: String
    let emailVerifiedAt: String?
    let firstName: String
    let id: Int
    let image: String
    let lastName: String
    let location: String? // Make it optional
    let pmLastFour: String? // Make it optional
    let pmType: String? // Make it optional
    let stripeId: String
    let trialEndsAt: String? // Make it optional
    let updatedAt: String

    // Define custom keys for snake case properties
    private enum CodingKeys: String, CodingKey {
        case clientId = "client_id"
        case createdAt = "created_at"
        case emailVerifiedAt = "email_verified_at"
        case firstName = "first_name"
        case lastName = "last_name"
        case pmLastFour = "pm_last_four"
        case pmType = "pm_type"
        case stripeId = "stripe_id"
        case trialEndsAt = "trial_ends_at"
        // ... other properties ...
    }
}

why am I getting: Type 'User' does not conform to protocol 'Decodable'

Accepted Reply

Or try just removing this line:

decoder.keyDecodingStrategy = .convertFromSnakeCase // Configure snake case decoding

You are already supplying an explicit enum CodingKeys that defines the key conversion, so asking for additional conversion produces unexpected results.

  • @Scott it seems convertFromSnakeCase and underscores are incompatible. Removing any o the 2 solves the issue. As exposed in Apple documentation, with convertFromSnakeCase, fee_fi_fo_fum converts to: feeFiFoFum: underscores are removed and replace by following uppercase.

Add a Comment

Replies

That's just because your codingKeys are not complete, there are missing cases.

No more error once completed:

    private enum CodingKeys: String, CodingKey {
        case clientId = "client_id"
        case createdAt = "created_at"
        case description = "description"
        case email = "email"
        case emailVerifiedAt = "email_verified_at"
        case firstName = "first_name"
        case id = "id"
        case image = "image"
        case lastName = "last_name"
        case location = "location"
        case pmLastFour = "pm_last_four"
        case pmType = "pm_type"
        case stripeId = "stripe_id"
        case trialEndsAt = "trial_ends_at"
        case updatedAt = "updatedAt"
        // ... other properties ...
    }

YAY thanks, no errors now apart from when I log in.

Error parsing response data: keyNotFound(CodingKeys(stringValue: "client_id", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "user", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "client_id", intValue: nil) ("client_id").", underlyingError: nil))

What is the data you try decoding ?

Please show the result of the print

            print(String(data: data, encoding: .utf8))

You probably have no client_id value.

Response data: {"user":{"id":2,"client_id":1,"first_name":"John","last_name":"Biddulph","email":"John.***[at]gmail.com","email_verified_at":null,"image":"1690027187.jpeg","location":null,"description":"Artist","created_at":"2023-07-22T10:51:57.000000Z","updated_at":"2023-07-22T11:59:47.000000Z","stripe_id":"cus_OJFvSSUgRbDTIi","pm_type":null,"pm_last_four":null,"trial_ends_at":null},"token":"46|ZZZjWGOQusPzsv5AA8yCmUpndoDikZha7F0lspe2"}

underscore (client_id) cause problems : https://stackoverflow.com/questions/55816336/swift-jsondecoder-coding-keys-not-working-with-underscore

So, when you get the json, you have to remove them from keys before decoding.

Or try just removing this line:

decoder.keyDecodingStrategy = .convertFromSnakeCase // Configure snake case decoding

You are already supplying an explicit enum CodingKeys that defines the key conversion, so asking for additional conversion produces unexpected results.

  • @Scott it seems convertFromSnakeCase and underscores are incompatible. Removing any o the 2 solves the issue. As exposed in Apple documentation, with convertFromSnakeCase, fee_fi_fo_fum converts to: feeFiFoFum: underscores are removed and replace by following uppercase.

Add a Comment

I'm still getting:

Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "user", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "updatedAt", intValue: nil) ("updatedAt").", underlyingError: nil))

I'm still getting:

still getting with what code precisely ? Removing snakeCase ? Removingundesrcore (both in codingKeys AND in the JSON you receive) ?

But you should first close the thread on the initial question's answer and create a new one, to avoid never ending threads.