swift - 如何强制 Swift 的 JSON 加载器从字符串中解析十进制
问题描述
我正在尝试Item
从 json 文件中初始化一个 s 数组。为此,我按照Apple 的教程 re: 去做(算法在 data.swift 中,但我也会发布一个精简版)我的问题是我从中提取数据的 API 提供了小数引号导致我得到错误
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "average_cost", intValue: nil)], debugDescription: "预计解码 Double但找到了一个字符串/数据。”,基础错误:无))
Apple 的 json 解码器所期望的:
[{
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": 0.00, // Doubles without quotation marks
"otc_price": 0.00,
"dealer_price": 0.00,
"ctc_price": 0.00
}]
我的 API 中的示例数据保存在items.json
:
[{
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": "0.00",
"otc_price": "0.00",
"dealer_price": "0.00",
"ctc_price": "0.00"
}]
我可能会重写我的 API 以提供不带引号的小数和整数,但是它已经被其他应用程序使用,所以我不想冒险破坏某些东西。
那么有没有办法告诉解码忽略引号?
项目结构:
struct Item : Decodable {
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
加载功能:
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
调用它:
let items: [Item] = load("items.json")
print(items)
解决方案
以下是我将如何实现它:
struct Item : Decodable {
let company: String
let itemClass: String
let name: String
let stock: Int
let averageCost: Decimal
let otcPrice: Decimal
let dealerPrice: Decimal
let ctcPrice: Decimal
enum CodingKeys: String, CodingKey {
case company
case itemClass = "item_class"
case name
case stock
case averageCost = "average_cost"
case otcPrice = "otc_price"
case dealerPrice = "dealer_price"
case ctcPrice = "ctc_price"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.company = try container.decode(String.self, forKey: .company)
self.itemClass = try container.decode(String.self, forKey: .itemClass)
self.name = try container.decode(String.self, forKey: .name)
self.stock = try container.decode(Int.self, forKey: .stock)
guard
let averageCost = Decimal(string: try container.decode(String.self, forKey: .averageCost))
else {
throw DecodingError.dataCorruptedError(forKey: .averageCost, in: container, debugDescription: "not a Decimal.")
}
guard
let otcPrice = Decimal(string: try container.decode(String.self, forKey: .otcPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .otcPrice, in: container, debugDescription: "not a Decimal.")
}
guard
let dealerPrice = Decimal(string: try container.decode(String.self, forKey: .dealerPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .dealerPrice, in: container, debugDescription: "not a Decimal.")
}
guard
let ctcPrice = Decimal(string: try container.decode(String.self, forKey: .ctcPrice))
else {
throw DecodingError.dataCorruptedError(forKey: .ctcPrice, in: container, debugDescription: "not a Decimal.")
}
self.averageCost = averageCost
self.otcPrice = otcPrice
self.dealerPrice = dealerPrice
self.ctcPrice = ctcPrice
}
}
或者,您可以将 String 属性保留在模型中,并在访问时转换为小数
struct Item : Decodable {
let company: String
let itemClass: String
let name: String
let stock: Int
let _averageCost: String
let _otcPrice: String
let _dealerPrice: String
let _ctcPrice: String
enum CodingKeys: String, CodingKey {
case company
case itemClass = "item_class"
case name
case stock
case _averageCost = "average_cost"
case _otcPrice = "otc_price"
case _dealerPrice = "dealer_price"
case _ctcPrice = "ctc_price"
}
var averageCost: Decimal {
return Decimal(string: _averageCost) ?? .zero
}
// ... and so on for the other properties
}