如何使用 Swift 结构正确解码嵌套的 JSON 对象
How to properly decode nested JSON objects with Swift structs
意向:
通过 Coinmarketcap API 接收加密货币价格数据,将其解码为 SWIFT 中的自定义结构,并可能将该数据存储在数据库(CoreData 或 SQLite)中。
上下文:
我在 JSONDecoder().decode
上收到以下错误:
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
问题:
- 如何正确解释该错误?我解码错了什么?
- 我收到的数据格式是否正确?看起来不像
正确的 JSON.
代码:
import UIKit
import PlaygroundSupport
// Defining structures
struct RootObject: Decodable {
let status: [String: StatusObject?]
let data: DataObject?
}
struct StatusObject: Decodable {
let credit_count: Int?
let elapsed: Int?
let error_code: Int?
let timestamp: String?
}
struct DataObject: Decodable {
let amount: Int?
let id: Int?
let last_updated: String?
let name: String?
let quote: [QuoteObject]?
let symbol: String?
}
struct QuoteObject: Decodable {
let usd: String?
}
struct usdObject: Decodable {
let last_updated: String?
let price: String?
}
//Configuring URLSession
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["X-CMC_PRO_API_KEY": "<removed>",
"Accept": "application/json",
"Accept-Encoding": "deflate, gzip"]
let session = URLSession(configuration: config)
let url = URL(string: "https://sandbox-api.coinmarketcap.com/v1/tools/price-conversion?convert=USD&amount=1&symbol=BTC")!
//Making and handling a request
let task = session.dataTask(with: url) { data, response, error in
guard error == nil else {
print ("error: \(error!)")
return
}
guard let content = data else {
print("No data")
return
}
//Serializing and displaying the received data
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any]
else {
print("Not containing JSON")
return
}
print(json)
//Trying to decode
do {
let prices = try JSONDecoder().decode(RootObject.self, from: data!)
print(prices)
} catch let decodeError {
print("Error serializing json:", decodeError)
}
}
task.resume()
数据响应及报错:
["status": {
"credit_count" = 1;
elapsed = 6;
"error_code" = 0;
"error_message" = "<null>";
timestamp = "2019-02-16T11:10:22.147Z";
}, "data": {
amount = 1;
id = 1;
"last_updated" = "2018-12-22T06:08:23.000Z";
name = Bitcoin;
quote = {
USD = {
"last_updated" = "2018-12-22T06:08:23.000Z";
price = "3881.88864625";
};
};
symbol = BTC;
}]
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
编辑 1:
正确序列化JSON:
{
"status": {
"timestamp": "2019-02-16T18:54:05.499Z",
"error_code": 0,
"error_message": null,
"elapsed": 6,
"credit_count": 1
},
"data": {
"id": 1,
"symbol": "BTC",
"name": "Bitcoin",
"amount": 1,
"last_updated": "2018-12-22T06:08:23.000Z",
"quote": {
"USD": {
"price": 3881.88864625,
"last_updated": "2018-12-22T06:08:23.000Z"
}
}
}
}
结构中有很多问题。
主要问题是 data
的值是一个字典,它被解码为一个结构而不是另一个字典。其他问题是 id
的类型是 String
而 price
是 Double
.
像 Coinmarketcap
这样的 API 发送可靠的数据,因此不要将所有内容都声明为可选。删除问号。
下面的结构能够解码 JSON。由于键改变,引号被解码成字典。添加 .convertFromSnakeCase
密钥解码策略以获得 camelCased 密钥。通过添加适当的日期解码策略,日期被解码为 Date
。
我删除了除 DataObject
之外的所有冗余 ...Object
项,因为 Data
结构已经存在。
struct Root: Decodable {
let status: Status
let data: DataObject
}
struct Status: Decodable {
let creditCount: Int
let elapsed: Int
let errorCode: Int
let timestamp: Date
}
struct DataObject: Decodable {
let amount: Int
let id: String
let lastUpdated: Date
let name: String
let quote: [String:Quote]
let symbol: String
}
struct Quote: Decodable {
let lastUpdated: Date
let price: Double
}
//Trying to decode
do {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
let quotes = result.data.quote
for (symbol, quote) in quotes {
print(symbol, quote.price)
}
} catch {
print(error)
}
意向:
通过 Coinmarketcap API 接收加密货币价格数据,将其解码为 SWIFT 中的自定义结构,并可能将该数据存储在数据库(CoreData 或 SQLite)中。
上下文:
我在 JSONDecoder().decode
上收到以下错误:
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
问题:
- 如何正确解释该错误?我解码错了什么?
- 我收到的数据格式是否正确?看起来不像 正确的 JSON.
代码:
import UIKit
import PlaygroundSupport
// Defining structures
struct RootObject: Decodable {
let status: [String: StatusObject?]
let data: DataObject?
}
struct StatusObject: Decodable {
let credit_count: Int?
let elapsed: Int?
let error_code: Int?
let timestamp: String?
}
struct DataObject: Decodable {
let amount: Int?
let id: Int?
let last_updated: String?
let name: String?
let quote: [QuoteObject]?
let symbol: String?
}
struct QuoteObject: Decodable {
let usd: String?
}
struct usdObject: Decodable {
let last_updated: String?
let price: String?
}
//Configuring URLSession
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["X-CMC_PRO_API_KEY": "<removed>",
"Accept": "application/json",
"Accept-Encoding": "deflate, gzip"]
let session = URLSession(configuration: config)
let url = URL(string: "https://sandbox-api.coinmarketcap.com/v1/tools/price-conversion?convert=USD&amount=1&symbol=BTC")!
//Making and handling a request
let task = session.dataTask(with: url) { data, response, error in
guard error == nil else {
print ("error: \(error!)")
return
}
guard let content = data else {
print("No data")
return
}
//Serializing and displaying the received data
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any]
else {
print("Not containing JSON")
return
}
print(json)
//Trying to decode
do {
let prices = try JSONDecoder().decode(RootObject.self, from: data!)
print(prices)
} catch let decodeError {
print("Error serializing json:", decodeError)
}
}
task.resume()
数据响应及报错:
["status": {
"credit_count" = 1;
elapsed = 6;
"error_code" = 0;
"error_message" = "<null>";
timestamp = "2019-02-16T11:10:22.147Z";
}, "data": {
amount = 1;
id = 1;
"last_updated" = "2018-12-22T06:08:23.000Z";
name = Bitcoin;
quote = {
USD = {
"last_updated" = "2018-12-22T06:08:23.000Z";
price = "3881.88864625";
};
};
symbol = BTC;
}]
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
编辑 1:
正确序列化JSON:
{
"status": {
"timestamp": "2019-02-16T18:54:05.499Z",
"error_code": 0,
"error_message": null,
"elapsed": 6,
"credit_count": 1
},
"data": {
"id": 1,
"symbol": "BTC",
"name": "Bitcoin",
"amount": 1,
"last_updated": "2018-12-22T06:08:23.000Z",
"quote": {
"USD": {
"price": 3881.88864625,
"last_updated": "2018-12-22T06:08:23.000Z"
}
}
}
}
结构中有很多问题。
主要问题是 data
的值是一个字典,它被解码为一个结构而不是另一个字典。其他问题是 id
的类型是 String
而 price
是 Double
.
像 Coinmarketcap
这样的 API 发送可靠的数据,因此不要将所有内容都声明为可选。删除问号。
下面的结构能够解码 JSON。由于键改变,引号被解码成字典。添加 .convertFromSnakeCase
密钥解码策略以获得 camelCased 密钥。通过添加适当的日期解码策略,日期被解码为 Date
。
我删除了除 DataObject
之外的所有冗余 ...Object
项,因为 Data
结构已经存在。
struct Root: Decodable {
let status: Status
let data: DataObject
}
struct Status: Decodable {
let creditCount: Int
let elapsed: Int
let errorCode: Int
let timestamp: Date
}
struct DataObject: Decodable {
let amount: Int
let id: String
let lastUpdated: Date
let name: String
let quote: [String:Quote]
let symbol: String
}
struct Quote: Decodable {
let lastUpdated: Date
let price: Double
}
//Trying to decode
do {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
let quotes = result.data.quote
for (symbol, quote) in quotes {
print(symbol, quote.price)
}
} catch {
print(error)
}