将 JSON 转换为 Swift 时出现问题 "The data couldn’t be read because it isn’t in the correct format."
Problem while converting JSON to Swift "The data couldn’t be read because it isn’t in the correct format."
当我从 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)
}
``
试试这个(注意选项和 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()
}
响应模型
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
首先
从未print(error.localizedDescription)
.
在 JSONDecoder
捕获块中。您收到一条通用但毫无意义的错误消息。
始终打印整个错误,DecodingError
描述性很强
print(error)
您的代码包含三个主要错误,其中之一(错误 #3)多次出现
错误#1
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
表示根对象是一个数组,JSON开头明显是[
解决方法:解码[CryptListStruct].self
错误#2
dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "volume_1mth_usd", intValue: nil)], debugDescription: "Parsed JSON number <3699822674922524.74> does not fit in 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: "No value associated with key CodingKeys(stringValue: "id_icon", intValue: nil) ("id_icon").", underlyingError: nil))
表示在数组的第 5 项中(至少)缺少键 id_icon
。
解决方法:声明类型为可选
let idIcon : String?
dataTradeStart
、dataTradeEnd
、dataQuoteStart
、dataQuoteEnd
、dataOrderbookStart
、dataOrderbookEnd
、[=29=也会出现同样的错误], dataEnd
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)
当我从 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)
}
``
试试这个(注意选项和 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()
}
响应模型
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
首先
从未print(error.localizedDescription)
.
在 JSONDecoder
捕获块中。您收到一条通用但毫无意义的错误消息。
始终打印整个错误,DecodingError
描述性很强
print(error)
您的代码包含三个主要错误,其中之一(错误 #3)多次出现
错误#1
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
表示根对象是一个数组,JSON开头明显是[
解决方法:解码[CryptListStruct].self
错误#2
dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "volume_1mth_usd", intValue: nil)], debugDescription: "Parsed JSON number <3699822674922524.74> does not fit in 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: "No value associated with key CodingKeys(stringValue: "id_icon", intValue: nil) ("id_icon").", underlyingError: nil))
表示在数组的第 5 项中(至少)缺少键 id_icon
。
解决方法:声明类型为可选
let idIcon : String?
dataTradeStart
、dataTradeEnd
、dataQuoteStart
、dataQuoteEnd
、dataOrderbookStart
、dataOrderbookEnd
、[=29=也会出现同样的错误], dataEnd
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)