Swift 中的 JSON 解析(iOS)
Posted
技术标签:
【中文标题】Swift 中的 JSON 解析(iOS)【英文标题】:JSON Parsing in Swift (iOS) 【发布时间】:2020-09-24 02:14:56 【问题描述】:我想为我的 ios 应用程序解析一些 JSON 数据。我的应用程序此时可以工作,但我觉得如果我决定添加更多货币,我现在拥有的当前解决方案将无法扩展。该应用程序使用选择器视图在多种货币之间进行选择,然后执行 URLSession 数据任务来检索 JSON 数据并对其进行解析。我想知道是否有更好的方法来解析基于不同货币的 JSON 数据。
func parseJSON(_ data: Data, currency: String) -> CurrencyInfo?
let decoder = JSONDecoder()
do
switch currency
case "HKD":
let decodedData = try decoder.decode(BitcoinDataHKD.self, from: data)
return CurrencyInfo(price: decodedData.bpi.HKD.rate_float, time: decodedData.time.updatedISO)
case "GBP":
let decodedData = try decoder.decode(BitcoinDataGBP.self, from: data)
return CurrencyInfo(price: decodedData.bpi.GBP.rate_float, time: decodedData.time.updatedISO)
default:
fatalError()
catch
delegate?.didFailWithError(error: error)
return nil
你看,对于每种情况,我都有一个 Decodable 结构。理想情况下,我希望有一个可以处理所有情况的结构,因此在解析 JSON 时不需要使用长的 switch 语句。这是结构的示例。
struct BitcoinDataHKD: Decodable
let bpi: BPI
let time: Time
struct BPI: Decodable
let HKD: HKD
struct HKD: Decodable
let code: String
let rate_float: Double
struct BitcoinDataGBP: Decodable
let bpi: BPI
let time: Time
struct BPI: Decodable
let GBP: GBP
struct GBP: Decodable
let code: String
let rate_float: Double
struct Time: Decodable
let updatedISO: String
结构之间唯一不同的是 BPI 结构,可以是“GBP”或“HKD”。
这是来自 CoinDesk 的 JSON 的样子。
https://api.coindesk.com/v1/bpi/currentprice/HKD.json
https://api.coindesk.com/v1/bpi/currentprice/GBP.json
【问题讨论】:
【参考方案1】:您可以尝试执行以下操作:
struct BitcoinData: Decodable
let time: ISOTime
let bpi: [String: CoinInfo]
struct CoinInfo: Decodable
let code: String
let description: String
let rate_float: Double
struct ISOTime: Decodable
let updatedISO: String
【讨论】:
【参考方案2】:您可以创建一个模型来解析所有货币并使用[String:Currency]
作为bpi
的类型,而不是创建另一个级别struct/class
。
struct BitcoinData: Decodable
let time: Time
let bpi: [String:Currency]
struct Time: Decodable
let updatedISO: String
struct Currency: Decodable
let code: String
let rateFloat: Double
注意:使用 camel-case 来命名变量。示例:使用rateFloat
和rate_float
。
由于 JSON 数据中的键是蛇形大小写的,因此有 2 个选项,
1. 实现enum CodingKeys
like,
struct Currency: Decodable
let code: String
let rateFloat: Double
enum CodingKeys: String, CodingKey
case code
case rateFloat = "rate_float"
2.或者你可以在解析时将JSONDecoder's
keyDecodingStrategy
设置为convertFromSnakeCase
,即
do
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase //here...
let response = try decoder.decode(BitcoinData.self, from: data)
print(response)
catch
print(error)
【讨论】:
【参考方案3】:将bpi
数据视为字典,以便能够使用通用结构。在处理货币汇率时,使用 Decimal 比使用 Double 作为小数部分要好得多,所以我使用汇率的字符串表示形式,并使用 NumberFormatter 将其转换为 Decimal。
struct Root: Decodable
let bpi: [String: BitcoinPriceIndex]
我忽略了顶层的其他属性以专注于重要的部分。下面是保存费率的结构,它有一个自定义的初始化,所以我们可以转换为十进制
struct BitcoinPriceIndex: Decodable
private static var rateFormatter: NumberFormatter
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .decimal
return formatter
let currencyCode: String
let rate: Decimal
enum CodingKeys: String, CodingKey
case currencyCode = "code"
case rate
init(from decoder: Decoder) throws
let container = try decoder.container(keyedBy: CodingKeys.self)
currencyCode = try container.decode(String.self, forKey: .currencyCode)
let rateStr = try container.decode(String.self, forKey: .rate)
rate = BitcoinPriceIndex.rateFormatter.number(from: rateStr)?.decimalValue ?? 0
和解码部分
do
let result = try JSONDecoder().decode(Root.self, from: data)
let rates = result.bpi.values
for rate in rates
print(rate.currencyCode, rate.rate)
catch
print(error)
10309.7535 美元 7841.9078 英镑
在上述解决方案中,如果格式化程序失败,速率属性将设置为 0,另一种解决方案是在这种情况下回退到双精度值。
将其添加到CodingKeys
枚举(但不是作为属性)
case rate_float
并将init
中的解码改为
if let rateFromString = BitcoinPriceIndex.rateFormatter.number(from: rateStr)?.decimalValue
rate = rateFromString
else
rate = try container.decode(Decimal.self, forKey: .rate_float)
【讨论】:
以上是关于Swift 中的 JSON 解析(iOS)的主要内容,如果未能解决你的问题,请参考以下文章
如何以正确的方式在 IOS SWIFT 3 中解析 Google 距离矩阵 API JSON