Search code examples
jsonswiftjsondecoder

Decoding JSON array wrapped inside dictionary using Swift 5


{
  "records": [
    {
      "id": 1,
      "customers_name": "Acme 1"
    },
    {
      "id": 2,
      "customers_name": "Acme2"
    }    
  ]
}

This is my really simple JSON scheme, but I can't get JSONDecoder() to work. My error code is:

Expected to decode Array but found a dictionary instead.

Here are my two files that I'm currently using:

Customer.swift

struct Customer: Decodable, Identifiable {

    public var id: String
    public var customers_name: String

    enum CodingKeys: String, CodingKey {
       case id = "id"
       case customers_name = "customers_name"
    }

    init(from decoder: Decoder) throws{
        let container = try decoder.container(keyedBy: CodingKeys.self)
            id = try container.decode(String.self, forKey: .id)
            customers_name = (try container.decodeIfPresent(String.self, forKey: .customers_name)) ?? "Unknown customer name"
    }
}

CustomerFetcher.swift

import Foundation

public class CustomerFetcher: ObservableObject {
    @Published var customers = [Customer]()

    init(){
        load()
    }

    func load() {
        let url = URL(string: "https://somedomain.com/customers.json")!

        URLSession.shared.dataTask(with: url) {(data,response,error) in
            do {
                if let d = data {
                    print(d)
                    let decodedLists = try JSONDecoder().decode([Customer].self, from: d)
                    DispatchQueue.main.async {
                        self.customers = decodedLists
                    }
                } else {
                    print("No Data")
                }
            } catch {
                print (error)
            }

        }.resume()

    }
}

I believe it's because of this nested JSON structure and tried so many things, but still can't get it working.

Thank you so much, if anyone would help me out!


Solution

  • You are forgetting the wrapping object:

    struct RecordList<T: Decodable>: Decodable {
        let records: [T]
    }
    
    
    let decodedLists = try JSONDecoder().decode(RecordList<Customer>.self, from: d)
    DispatchQueue.main.async {
        self.customers = decodedLists.records
    }
    

    Also note the Customer can be reduced to:

    struct Customer: Decodable, Identifiable {
       public var id: String
       public var customersName: String
    
       enum CodingKeys: String, CodingKey {
          case id
          case customersName = "customers_name"
       }
    }
    

    You can also setup your JSONDecoder to convert underscores to camel case automatically. Then you won't even need the CodingKeys.