一尘不染

Swift 4 JSON Decodable解码类型更改的最简单方法

swift

借助Swift 4的Codable协议,可以很好地了解标准日期和数据转换策略。

鉴于JSON:

{
    "name": "Bob",
    "age": 25,
    "tax_rate": "4.25"
}

我想将其强制为以下结构

struct ExampleJson: Decodable {
    var name: String
    var age: Int
    var taxRate: Float

    enum CodingKeys: String, CodingKey {
       case name, age 
       case taxRate = "tax_rate"
    }
}

日期解码策略可以将基于字符串的日期转换为日期。

有什么用基于String的Float做到的吗

否则,我将不得不使用CodingKey引入String并使用计算get:

    enum CodingKeys: String, CodingKey {
       case name, age 
       case sTaxRate = "tax_rate"
    }
    var sTaxRate: String
    var taxRate: Float { return Float(sTaxRate) ?? 0.0 }

这种麻烦让我做的维护工作似乎超出了需要。

这是最简单的方法还是其他类型转换与DateDecodingStrategy类似的东西?

更新 :我应该注意:我也走了重写的路线

init(from decoder:Decoder)

但这是相反的方向,因为它迫使我自己做这一切。


阅读 222

收藏
2020-07-07

共1个答案

一尘不染

不幸的是,我认为当前的JSONDecoderAPI中不存在这样的选项。只有一个选项可以特殊的
浮点值
与字符串表示形式相互转换。

手动解码的另一种可能的解决方案是为可以对其表示编码和从其表示解码的Codable任何内容定义包装器类型:LosslessStringConvertible``String

struct StringCodableMap<Decoded : LosslessStringConvertible> : Codable {

    var decoded: Decoded

    init(_ decoded: Decoded) {
        self.decoded = decoded
    }

    init(from decoder: Decoder) throws {

        let container = try decoder.singleValueContainer()
        let decodedString = try container.decode(String.self)

        guard let decoded = Decoded(decodedString) else {
            throw DecodingError.dataCorruptedError(
                in: container, debugDescription: """
                The string \(decodedString) is not representable as a \(Decoded.self)
                """
            )
        }

        self.decoded = decoded
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(decoded.description)
    }
}

然后,您可以拥有这种类型的属性并使用自动生成的Codable一致性:

struct Example : Codable {

    var name: String
    var age: Int
    var taxRate: StringCodableMap<Float>

    private enum CodingKeys: String, CodingKey {
        case name, age
        case taxRate = "tax_rate"
    }
}

虽然很不幸,但是现在您必须就taxRate.decoded如何与Float价值互动进行讨论。

但是,您总是可以定义一个简单的转发计算属性以减轻这种情况:

struct Example : Codable {

    var name: String
    var age: Int

    private var _taxRate: StringCodableMap<Float>

    var taxRate: Float {
        get { return _taxRate.decoded }
        set { _taxRate.decoded = newValue }
    }

    private enum CodingKeys: String, CodingKey {
        case name, age
        case _taxRate = "tax_rate"
    }
}

尽管这还没有达到应有的效果,但希望更高版本的JSONDecoderAPI会包含更多自定义解码选项,或者能够在CodableAPI本身内表达类型转换。

但是,创建包装器类型的一个优点是,也可以使用它来简化手动解码和编码。例如,使用手动解码:

struct Example : Decodable {

    var name: String
    var age: Int
    var taxRate: Float

    private enum CodingKeys: String, CodingKey {
        case name, age
        case taxRate = "tax_rate"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
        self.taxRate = try container.decode(StringCodableMap<Float>.self,
                                            forKey: .taxRate).decoded
    }
}
2020-07-07