【问题标题】:How to decode any json value to string with Decoadable object in Swift?如何在 Swift 中使用 Decoadable 对象将任何 json 值解码为字符串?
【发布时间】:2021-12-28 17:07:26
【问题描述】:

根据我的问题,我想将 json 的每个字段解码为字符串值。

我的 json 看起来像这样

{ name: "admin_tester",
  price: 99.89977202, 
  no: 981,
  id: "nfs-998281998",
  amount: 98181819911019.828289291329 }

我想创建这样的结构

struct StockNFS: Decodable {
     let name: String?
     let price: String?
     let no: String?
     let id: String?
     let amount: String?
}

但是如果我这样声明我的结构,当我使用 json 解码时,我会得到错误不匹配类型

我之所以要将每个值都映射到字符串,是因为如果我对priceamount 使用双精度或小数,编码后有时值会不正确。例如 0.125,我会得到 0.124999999。

我只想接收字符串类型的任何数据,以便在 ui 上显示(而不是编辑或操作值)

我将不胜感激。非常感谢。

【问题讨论】:

  • 您需要一个自定义 init(from:) 来解码您的双精度值并执行正确的舍入。一些注意事项,为什么不使用Decimal 而不是String 作为价格和金额,以及为什么需要将Int 值转换为字符串,因为Int 没有舍入问题。为什么你的结构中的一切都是可选的?
  • @Joakim 非常感谢您的回复,我不使用十进制,因为有时我从 json 得到错误的值映射,例如 0.125,解码后得到 0.12499999。我设置 optional 是因为我想设置可为空的值(也许有时 api 在解码后没有返回该键,它可以是 nil。)
  • 你能给我一些关于自定义 init() 的例子吗? @Joakim

标签: ios json swift codable decodable


【解决方案1】:

为了避免浮点问题,我们可以使用 String 或 Decimal 类型作为键的价格和金额。在这两种情况下,我们都不能直接解码为任何一种类型,但我们首先需要使用给定的 Double 类型,因此我们需要一个自定义的 init。

第一种情况是使用字符串(我认为没有理由默认使用可选字段,如果任何字段实际上可以为零,请更改此设置)

struct StockNFS: Codable {
    let name: String
    let price: String
    let no: Int
    let id: String
    let amount: String

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let priceValue = try container.decode(Double.self, forKey: .price)
        price = "\(priceValue.roundToDecimal(8))"
        //... rest of the values
    }
}

四舍五入的方法灵感来自this优秀答案

 extension Double {
    func roundToDecimal(_ fractionDigits: Int) -> Double {
        let multiplier = pow(10, Double(fractionDigits))
        return (self * multiplier).rounded() / multiplier
    }
}

做同样的事情,但我们使用数字类型Decimal

struct StockNFS2: Codable {
    let name: String
    let price: Decimal
    let no: Int
    let id: String
    let amount: Decimal
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let priceValue = try container.decode(Double.self, forKey: .price)
        price = Decimal(priceValue).round(.plain, precision: 8)
        //... rest of the values
    }
}

再次取整方法的灵感来自相同的答案

extension Decimal {
    func round(_ mode: Decimal.RoundingMode, precision: Int = 2) -> Decimal {
        var result = Decimal()
        var value = self
        NSDecimalRound(&result, &value, precision, mode)
        return result
    }
}

【讨论】:

  • 谢谢 Joakim,我会试试的,我会再次告诉你我的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2015-08-03
  • 1970-01-01
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多