0

When I try to decode my JSON in SwiftUI it always gives an error:

keyNotFound(CodingKeys(stringValue: "is_paid", intValue: nil)

Some values like Double do seem to work but other types seems to give these type of errors. I have been searching for this answer for over 2 days now and I'm breaking my head over this.

Model

struct Invoices: Decodable, Hashable, Identifiable {
    let id: Int
    let is_paid: Int
    let total: Double
    let invoice_id: String
}

Somehow id and total give the correct value:

success([KlantenTest.InvoiceTest(id: 22, total: 30.0), KlantenTest.InvoiceTest(id: 26, total: 29.9959), KlantenTest.InvoiceTest(id: 28, total: 363.0), KlantenTest.InvoiceTest(id: 31, total: 240.1608), KlantenTest.InvoiceTest(id: 32, total: 29.9959)])

Function

func getCategorByAPI() -> Void {
    let url = URL(string: "\(self.Api_URL)/api/v1/webhook/invoices/\(self.Api_token)/\(self.UserToken ?? self.Default)")!
    
    var request  = URLRequest(url: url)
    
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.setValue("Bearer \(self.Token ?? "")", forHTTPHeaderField: "Authorization")

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    URLSession.shared.jsonDecodableTask(with: request, decoder: decoder) { (result: Result<[InvoiceTest], Error>) in
        
        print(result)
        switch result {
            case .success(let person):
                print("InvoiceID: \(person)")
            case .failure(let error):
                print(error)
        }
    }.resume()
}

Extension

enum URLError: Error {
    case noData, decodingError
}

extension URLSession {
    /// A type safe URL loader that either completes with success value or error with Error
    func jsonDecodableTask<T: Decodable>(with url: URLRequest, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, Error>) -> Void) -> URLSessionDataTask {
       self.dataTask(with: url) { (data, response, error) in
            DispatchQueue.main.async {
                guard error == nil else {
                    completion(.failure(error!))
                    return
                }
                guard let data = data, let _ = response else {
                    completion(.failure(URLError.noData))
                    return
                }
                do {
                    let decoded = try decoder.decode(T.self, from: data)
                    completion(.success(decoded))
                } catch  {
                    completion(.failure(error))
                }
            }
        }
    }

    func jsonDecodableTask<T: Decodable>(with url: URL, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, Error>) -> Void) -> URLSessionDataTask {
       self.jsonDecodableTask(with: URLRequest(url: url), decoder: decoder, completion: completion)
    }
}

JSON

[
    {
        "id": 28,
        "invoice_id": "20210126075159",
        "total": 363,
        "is_paid": 1,
    },
    {
        "id": 31,
        "invoice_id": "20210126075161",
        "total": 240.1608,
        "is_paid": 1,
    },
    {
        "id": 32,
        "invoice_id": "20210126075162",
        "total": 29.9959,
        "is_paid": 1,
    }
]

As you can see the values are actually there so I don't understand where the error is coming from because when I try function below, it DOES work with the same model Invoices

 let Url = URL(string: "\(self.Api_URL)/api/v1/webhook/invoices/\(self.Api_token)/\(self.UserToken ?? self.Default)")!

    var request = URLRequest(url: Url)
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.setValue("Bearer \(self.Token ?? "")", forHTTPHeaderField: "Authorization")
    
    URLSession.shared.dataTask(with: request) { (data, res, err) in
        //check response status and err
        guard let data = data else { return }
        
        do {
            let invoice = try JSONDecoder().decode([Invoices].self, from: data)
            self.invoices = invoice
            
        } catch {
            print("Failed to decode: \(error)")
        }
    }.resume()

PS. I also tried to call it through [GetInvoices]

struct GetInvoices: Decodable {
    let invoices: [Invoices]
}

But that can't even find the id or the total that Invoices can find, so that's no use.

Code used from: https://stackoverflow.com/a/59838319/5100123

1 Answer 1

1

Your problem is in: .convertFromSnakeCase

As stated in reference: https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase

is_paid is being decoded into isPaid, same for invoice_id.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes! This fixes it. Thank you so much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.