Parsing nested JSON using Decodable in Swift

115 views Asked by At

I'm trying to parse this JSON response

    {
    "payload": {
        "bgl_category": [{
            "number": "X",
            "name": "",
            "parent_number": null,
            "id": 48488,
            "description": "Baustellenunterk\u00fcnfte, Container",
            "children_count": 6
        }, {
            "number": "Y",
            "name": "",
            "parent_number": null,
            "id": 49586,
            "description": "Ger\u00e4te f\u00fcr Vermessung, Labor, B\u00fcro, Kommunikation, \u00dcberwachung, K\u00fcche",
            "children_count": 7
        }]
    },
    "meta": {
        "total": 21
    }
}

What I'm interested to view in my TableViewCell are only the number and description

here is what I tried to far:

    //MARK: - BGLCats
struct BGLCats: Decodable {

        let meta : Meta!
        let payload : Payload!
        
}

//MARK: - Payload
struct Payload: Decodable {

        let bglCategory : [BglCategory]!
        
}

//MARK: - BglCategory
struct BglCategory: Decodable {

        let descriptionField : String
        let id : Int
        let name : String
        let number : String
        let parentNumber : Int
        
}

//MARK: - Meta
struct Meta: Decodable {

        let total : Int
        
}

API request:

    fileprivate func getBgls() {
        
        guard let authToken = getAuthToken() else {
            return
        }
        
        let headers  = [
            "content-type" : "application/json",
            "cache-control": "no-cache",
            "Accept"       : "application/json",
            "Authorization": "\(authToken)"
        ]
        
        let request = NSMutableURLRequest(url: NSURL(string: "https://api-dev.com")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
        
        request.allHTTPHeaderFields = headers
        
        let endpoint  = "https://api-dev.com"
        guard let url = URL(string: endpoint) else { return }
        
        URLSession.shared.dataTask(with: request as URLRequest) {(data, response, error) in
            guard let data = data else { return }
            
            do {
                let BGLList = try JSONDecoder().decode(BglCategory.self, from: data)
                print(BGLList)
                
                DispatchQueue.main.sync { [ weak self] in
                    self?.number = BGLList.number
                    self?.desc   = BGLList.descriptionField
//                    self?.id  = BGLList.id

                    print("Number: \(self?.number ?? "Unknown" )")
                    print("desc: \(self?.desc ?? "Unknown" )")
//                    print("id: \(self?.id ?? 0 )")
                }
            } catch let jsonError {
                print("Error Serializing JSON:", jsonError)
            }
            
       }.resume()
    }

But I'm getting error:

Error Serializing JSON: keyNotFound(CodingKeys(stringValue: "childrenCount", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"childrenCount\", intValue: nil) (\"childrenCount\").", underlyingError: nil))
2

There are 2 answers

0
New Dev On BEST ANSWER

There are a few issues here.

You created the model (mostly) correctly, but there're just two mismatches:

struct BglCategory: Decodable {

   let description : String // renamed, to match "description" in JSON
   let parentNum: Int?      // optional, because some values are null
   // ...
}

Second issue is that your model properties are camelCased whereas JSON is snake_cased. JSONDecoder has a .convertFromSnakeCase startegy to automatically handle that. You need to set it on the decoder prior to decoding.

Third issue is that you need to decode the root object BGLCats, instead of BglCategory.

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // set the decoding strategy

let bglCats = try decoder.decode(BGLCats.self, from: data) // decode BGLCats

let blgCategories = bglCats.payload.bglCategory
0
Patryk Budzinski On

The problem is that JSONDecoder doesn't know that for example bglCategory is represented in JSON payload as bgl_category. If the JSON name isn't the same as the variable name you need to implement CodingKeys to your Decodable

In your case:

struct BglCategory: Decodable {
  
  let descriptionField : String
  let id : Int
  let name : String
  let number : String
  let parentNumber : Int?
  
  enum CodingKeys: String, CodingKey {
    case id, name, number
    case descriptionField = "description"
    case parentNumber = "parent_number"
  }
}

struct Payload: Decodable {
  
  let bglCategory : [BglCategory]!
  
  enum CodingKeys: String, CodingKey {
    case bglCategory = "bgl_category"
  }
  
}