在Swift中使用Decodable解析嵌套的JSON

问题描述

我正在尝试解析此JSON响应

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

我想在TableViewCell中查看的只是numberdescription

这是我到目前为止想要做的:

    //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请求:

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

但是我遇到了错误

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

解决方法

这里有一些问题。

您(大多数情况下)正确地创建了模型,但是只有两个不匹配:

struct BglCategory: Decodable {

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

第二个问题是您的模型属性是 camelCased ,而JSON是 snake_cased 。 JSONDecoder具有.convertFromSnakeCase实例来自动处理。您需要先在解码器上进行设置。

第三个问题是,您需要解码根对象BGLCats,而不是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
,

问题是JSONDecoder不知道例如bglCategory 在JSON有效负载中表示为bgl_category。如果JSON名称与变量名称不同,则需要对CodingKeys实施Decodable

在您的情况下:

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"
  }
  
}