Search code examples
jsonswiftdecodable

Parsing nested JSON using Decodable in Swift


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))

Solution

  • 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