【问题标题】:Problem while converting JSON to Swift "The data couldn’t be read because it isn’t in the correct format."将 JSON 转换为 Swift 时出现问题“无法读取数据,因为它的格式不正确。”
【发布时间】:2021-11-13 09:07:31
【问题描述】:

当我从 coinapi.com (https://rest-sandbox.coinapi.io/v1/assets/?apikey=72869C8A-D49B-4DC5-9A3B-17D9804AEE97) 转换 JSON 时,我遇到了一个问题“无法读取数据,因为它的格式不正确”。

转换后的结果是:

struct CryptListStruct: Codable {

    let assetID, name: String
    let typeIsCrypto: Int
    let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String
    let dataTradeStart, dataTradeEnd: String
    let dataSymbolsCount: Int
    let volume1HrsUsd, volume1DayUsd: Double
    let volume1MthUsd: Int
    let priceUsd: Double?
    let idIcon, dataStart, dataEnd: String

    enum CodingKeys: String, CodingKey {
        case assetID = "asset_id"
        case name
        case typeIsCrypto = "type_is_crypto"
        case dataQuoteStart = "data_quote_start"
        case dataQuoteEnd = "data_quote_end"
        case dataOrderbookStart = "data_orderbook_start"
        case dataOrderbookEnd = "data_orderbook_end"
        case dataTradeStart = "data_trade_start"
        case dataTradeEnd = "data_trade_end"
        case dataSymbolsCount = "data_symbols_count"
        case volume1HrsUsd = "volume_1hrs_usd"
        case volume1DayUsd = "volume_1day_usd"
        case volume1MthUsd = "volume_1mth_usd"
        case priceUsd = "price_usd"
        case idIcon = "id_icon"
        case dataStart = "data_start"
        case dataEnd = "data_end"
    }
}

我认为这是因为那里有很多数据,而且它们不一样。如何获得正确的模型?

这就是我的函数的样子:

    
    
    func getCryptList(_ completion: @escaping (CryptListStruct) -> Void, _ error: @escaping (String) -> Void){
    let header: [String: String] = [:]
       self.get(url: "https://rest.coinapi.io/v1/assets/?apikey=367FB27A-371B-4DBD-AB81-E98AAFE857B2", header: header, completion: {
            (data) in
            do {
                guard let data = data else {return}
                let crpytList = try JSONDecoder().decode(CryptListStruct.self, from: data)
                DispatchQueue.main.async {
                    completion(crpytList)
                }
            } catch let err {
                       error(err.localizedDescription)
                   }
               }, error: error)
    }
``

【问题讨论】:

    标签: json swift jsonconvert


    【解决方案1】:

    首先

    从不print(error.localizedDescription)

    JSONDecoder 捕获块中。您会收到一条通用但毫无意义的错误消息。

    总是打印整个错误,DecodingErrors 非常具有描述性

    print(error)
    

    您的代码包含三个主要错误,其中一个(错误 #3)多次出现

    错误 #1

    typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "应解码 Dictionary 但找到了一个数组。强>”,基础错误:无))

    表示根对象为数组,JSON明确以[开头

    解决方案:解码[CryptListStruct].self


    错误 #2

    dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "volume_1mth_usd", intValue: nil)], debugDescription: "解析的 JSON 编号 不适合 Int。”,underlyingError: nil))

    表示接收到的值3699822674922524.74实际上是一个Double

    解决方案:声明

    let volume1MthUsd: Double
    

    错误 #3

    keyNotFound(CodingKeys(stringValue: "id_icon", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 4", intValue: 4)], debugDescription: "没有值关联键 CodingKeys(stringValue: "id_icon", intValue: nil) ("id_icon").", underlyingError: nil))

    表示在数组的第 5 项中(至少)缺少键 id_icon

    解决方案:将类型声明为可选

    let idIcon : String?
    

    dataTradeStartdataTradeEnddataQuoteStartdataQuoteEnddataOrderbookStartdataOrderbookEnddataStartdataEnd 出现同样的错误

    let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String?
    let dataTradeStart, dataTradeEnd : String?
    let dataStart, dataEnd: String?
    

    旁注:

    如果将assetID 替换为assetId 并添加convertFromSnakeCase 密钥解码策略,则可以删除整个 CodingKeys 枚举

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let crpytList = try decoder.decode([CryptListStruct].self, from: data)
    

    【讨论】:

      【解决方案2】:

      响应模型

      typealias CryptResult = [CryptListElement]
      
      // MARK: - CryptListElement
      struct CryptListElement: Codable {
          let assetID, name: String
          let typeIsCrypto: Int
          let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String?
          let dataTradeStart, dataTradeEnd: String?
          let dataSymbolsCount: Int
          let volume1HrsUsd, volume1DayUsd, volume1MthUsd: Double
          let priceUsd: Double?
          let idIcon, dataStart, dataEnd: String?
      
          enum CodingKeys: String, CodingKey {
              case assetID = "asset_id"
              case name
              case typeIsCrypto = "type_is_crypto"
              case dataQuoteStart = "data_quote_start"
              case dataQuoteEnd = "data_quote_end"
              case dataOrderbookStart = "data_orderbook_start"
              case dataOrderbookEnd = "data_orderbook_end"
              case dataTradeStart = "data_trade_start"
              case dataTradeEnd = "data_trade_end"
              case dataSymbolsCount = "data_symbols_count"
              case volume1HrsUsd = "volume_1hrs_usd"
              case volume1DayUsd = "volume_1day_usd"
              case volume1MthUsd = "volume_1mth_usd"
              case priceUsd = "price_usd"
              case idIcon = "id_icon"
              case dataStart = "data_start"
              case dataEnd = "data_end"
          }
      }
      

      解码器代码:

       let result = try? newJSONDecoder().decode(CryptResult.self, from: jsonData)
      

      我们需要使用 CryptResult 而不是 CryptListElement

      【讨论】:

        【解决方案3】:

        试试这个(注意选项和volume1MthUsd:Double),对我有用:

        struct CryptListStruct: Codable {
        
            let assetID, name: String
            let typeIsCrypto: Int
            let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String?
            let dataTradeStart, dataTradeEnd: String?
            let dataSymbolsCount: Int
            let volume1HrsUsd, volume1DayUsd, volume1MthUsd: Double
            let priceUsd: Double?
            let idIcon, dataStart, dataEnd: String?
        
            enum CodingKeys: String, CodingKey {
                case assetID = "asset_id"
                case name
                case typeIsCrypto = "type_is_crypto"
                case dataQuoteStart = "data_quote_start"
                case dataQuoteEnd = "data_quote_end"
                case dataOrderbookStart = "data_orderbook_start"
                case dataOrderbookEnd = "data_orderbook_end"
                case dataTradeStart = "data_trade_start"
                case dataTradeEnd = "data_trade_end"
                case dataSymbolsCount = "data_symbols_count"
                case volume1HrsUsd = "volume_1hrs_usd"
                case volume1DayUsd = "volume_1day_usd"
                case volume1MthUsd = "volume_1mth_usd"
                case priceUsd = "price_usd"
                case idIcon = "id_icon"
                case dataStart = "data_start"
                case dataEnd = "data_end"
            }
        }
        

        编辑1:

        这就是我测试答案的方式:

        import SwiftUI
        
        @main
        struct TestApp: App {
            var body: some Scene {
                WindowGroup {
                    ContentView()
                }
            }
        }
            struct ContentView: View {
            @State var cryptList = [CryptListStruct]()
            
            var body: some View {
                Text(cryptList.count > 0 ? "fetched \(cryptList.count)" : "fetching...")
                    .task {
                        cryptList = await fetchThem()
                        print("\n----> cryptList: \(cryptList.count) \n")
                    }
                // using closure
                //.onAppear {
                //    getCryptList() { list in
                //        cryptList = list
                //    }
                // }
            }
            
            private func fetchThem<T: Decodable>() async -> [T] {
                var url = URL(string: "https://rest-sandbox.coinapi.io/v1/assets/?apikey=72869C8A-D49B-4DC5-9A3B-17D9804AEE97")!
                do {
                    let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
                    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                        print(URLError(.badServerResponse))
                        return []
                    }
                    return try JSONDecoder().decode([T].self, from: data)
                }
                catch {
                    return []
                }
            }
        }
        

        EDIT2:使用闭包样式:

        private func getCryptList(_ completion: @escaping ([CryptListStruct]) -> Void) {
            URLSession.shared.dataTask(with: URL(string: "https://rest-sandbox.coinapi.io/v1/assets/?apikey=72869C8A-D49B-4DC5-9A3B-17D9804AEE97")!,
                                       completionHandler: { data, response, error in
                guard let data = data, error == nil else {
                    print("error: \(error?.localizedDescription as Optional)")
                    return
                }
                do {
                    let cryptList = try JSONDecoder().decode([CryptListStruct].self, from: data)
                    completion(cryptList)
                }
                catch {
                    print(String(describing: error))
                }
            }).resume()
        }
        

        【讨论】:

        • 为什么大多数键都清楚地在all字典中,但为什么将类型更改为可选?
        • 不,没有。但如果你愿意,你可以尝试不带可选的。让我们知道它是否有效。
        • 不工作。仍然存在问题“无法读取数据,因为它的格式不正确。”
        • 好的,我已经用一些非可选的 var 更新了我的答案。您还必须解码 CryptListStruct 数组,例如:[CryptListStruct]。对我来说没有任何问题。可以肯定的是,向我们展示您如何解码响应
        • 正如我在 commect 中所说,您必须解码 [CryptListStruct].self 而不是 CryptListStruct.self。您还必须返回[CryptListStruct]
        猜你喜欢
        • 2020-03-11
        • 2019-07-28
        • 2021-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-12
        • 2018-12-25
        • 1970-01-01
        相关资源
        最近更新 更多