如何使用 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))

问题:

  1. 如何正确解释该错误?我解码错了什么?
  2. 我收到的数据格式是否正确?看起来不像 正确的 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 的类型是 StringpriceDouble.

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