如何使用 jsonDecoder 处理来自 JSON 响应的动态键?

Posted

技术标签:

【中文标题】如何使用 jsonDecoder 处理来自 JSON 响应的动态键?【英文标题】:How to handle dynamic keys from a JSON response using jsonDecoder? 【发布时间】:2019-02-25 12:26:34 【问题描述】:

如何处理这个 JSON 并在 Swift 4 中使用解码器解析这个 JSON?我尝试了几次,但都失败了。我不明白如何处理这种 JSON 格式。

[
  
    products: 
      id: 69,
      name: "test",
      des: "Hi this is a test",
      sort_des: "this is a test category",
    ,
    documents: 
      0: 
        id: 1,
        name: "105gg_1992uu",
        citation: "This is citation for 105gg_1992uu",
        file: "105gg_1992uu.pdf",

       created_at: "2019-01-25 09:07:09",
        category_id: 69,
      ,
      1: 
        id: 2,
        name: "96tt-1997tt",
        citation: "This is citation for 96tt-1997tt",
        file: "96tt-1997tt.pdf",
        created_at: "2019-01-25 09:07:09",
        category_id: 69,
      ,

    ,

  
]

我尝试了以下代码。 这是我的模型课。

struct GenericCodingKeys: CodingKey 
    var stringValue: String
    var intValue: Int?
    init?(stringValue: String) 
        self.stringValue = stringValue
    
    init?(intValue: Int) 
        self.intValue = intValue
        self.stringValue = "\(intValue)"
    


struct Model : Codable 

  struct Documents : Codable 
        var id : Int?
        var name : String?
        var citation : String?
        var file : String?
        var created_at : String?
        var category_id : Int?## Heading ##

     private enum CodingKeys : String, CodingKey
            case id = "id"
            case name =  "name"
            case citation = "citation"
            case file = "file"
            case created_at = "created_at"
            case category_id = "category_id"
      
   

   struct Products : Codable 
        var id : Int?
        var name : String?
        var des : String?
        var sort_des : String?

      private enum CodingKeys: String, CodingKey
            case id = "id"
            case name = "name"
            case des = "des"
            case sort_des = "sort_des"
      
  

    var products : Products?
    var documents : [Documents]?
    private enum CodingKeys : String, CodingKey
        case products
        case documents
    
    init(from decoder: Decoder) throws 
        self.documents = [Documents]()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.products = try container.decode(Products.self, forKey: .products)
        let documents = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .documents)
        for doc in documents.allKeys
            let docEach = try documents.decode(Documents.self, forKey: doc)
            self.documents?.append(docEach)
        
    

这是我从那个 JSON 函数中获取数据

   class LatestStatuesVC: UIViewController,UITableViewDelegate,UITableViewDataSource 

    @IBOutlet var tableView: UITableView!
    var caseData : [Model]?
    var model : Model?
    var countCaseData = 0

    override func viewDidLoad() 
        super.viewDidLoad()
        downloadAllData()
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return countCaseData
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.cellLatestStatues, for: indexPath) as! LatestStatuesTableCell
        return cell
    

    //MARK: Download all documents into internal directory
    func downloadAllData()
        let url = URL(string: URLString.urlForGetDocuments)
        URLSession.shared.dataTask(with: url!)  (data, response, err) in
            DispatchQueue.main.async 
                do
                    if err == nil 
                        let products = try JSONDecoder().decode(Model.Products.self, from: data!)
                        let documentAll = try JSONDecoder().decode([Model.Documents].self, from: data!)
                        print(products.name as Any)
                        self.countCaseData = documentAll.count
                        for doc in documentAll
                            print(doc.name as Any)
                            print(doc.citation as Any)
                        
                    
                
                catch let err
                    print(err)
                
            
        .resume()
    


我收到此代码的此错误。

   typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

【问题讨论】:

您是否收到任何错误消息?你得到什么结果? 是的。我得到“typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary 但找到了一个数组。",underlyingError: nil )) "这个错误信息。 读取 JSON(实际上是无效的 JSON),它以 [ 开头,因此根对象显然是一个数组。这就是错误消息的内容。 【参考方案1】:

错误清楚地表明 JSON 的根对象是一个数组,但您尝试解码字典。

基本上你的结构太复杂了。如果您想通过摆脱数字字典键将documents 作为一个数组,只需在根 (Model) 结构中编写一个自定义初始化程序,它将documents 解码为字典并将按id 排序的值作为documents 数组。

struct Model : Decodable 
    let products : Product
    let documents : [Document]

    enum CodingKeys: String, CodingKey  case products, documents 

    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        products = try container.decode(Product.self, forKey: .products)
        let documentData = try container.decode([String:Document].self, forKey: .documents)
        documents = documentData.values.sorted(by: $0.id < $1.id)
    


struct Product: Decodable 
    let id : Int
    let name, description, sortDescription : String
    let type : String


struct Document: Decodable 
    let id, categoryId : Int
    let name, citation, file : String
    let createdAt : Date

然后解码JSON(假设data表示JSON为Data),则createdAt的值被解码为Date

let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do 
    let modelArray = try decoder.decode([Model].self, from: data)
    for model in modelArray 
        print("products:",model.products)
        print("documents:",model.documents)
    

 catch  print(error) 

convertFromSnakeCase 键解码策略将所有 snake_cased 键转换为 camelCased 结构成员,而无需指定任何 CodingKeys

【讨论】:

为什么使用“区域设置”。你能用几行详细说明你的代码吗? @vadian 顺便说一句,感谢您的评论。我认为它可以帮助我。但是当我编译您的代码时,会显示此错误:“valueNotFound(Swift.KeyedDecodingContainer(unknown context at 0x10c2d86c8).CodingKeys>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue:”Index 0 ", intValue: 0), CodingKeys(stringValue: "documents", intValue: nil), _DictionaryCodingKey(stringValue: "3876", intValue: 3876)], debugDescription: "无法获取键控解码容器 -- 改为找到空值。" , 底层错误: nil)) " 固定区域设置用于避免在格式化日期时出现意外行为。我的代码与您的相似,但更简单。 但是你的代码没有成功运行。出现上述错误。 我的答案是针对问题中的 JSON。如果您想要一个可行的解决方案,请发布 real JSON

以上是关于如何使用 jsonDecoder 处理来自 JSON 响应的动态键?的主要内容,如果未能解决你的问题,请参考以下文章

如何准备 API 响应以在 swift 中与 jsonDecoder 一起使用

Swift 项目中涉及到 JSONDecoder,网络请求,泛型协议式编程的一些记录和想法

Python 3 中的 JSONdecoder 错误。来自 API 的 Json

JSONDecoder 无法处理空响应

使用 JSONDecoder Swift 解码具有整数值的字符串键 [关闭]

JSONDecoder 找不到存在的密钥