首页 > 解决方案 > 如何强制 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)

标签: swift

解决方案


以下是我将如何实现它:

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
}

推荐阅读