Search code examples
jsonswiftdecodedecoding

SWIFTUI - Json decoding always returns keyNotFound


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


Solution

  • 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.