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

Dru*_*man 24 json swift swift4 codable

使用swift4的Codable协议,有很多级别的引擎日期和数据转换策略.

鉴于JSON:

{
    "name": "Bob",
    "age": 25,
    "tax_rate": "4.25"
}
Run Code Online (Sandbox Code Playgroud)

我想将它强制进入以下结构

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

    enum CodingKeys: String, CodingKey {
       case name, age 
       case taxRate = "tax_rate"
    }
}
Run Code Online (Sandbox Code Playgroud)

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

基于String的Float是否有某种功能

否则我一直坚持使用CodingKey引入一个String并使用计算得到:

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

这种方式比我看起来需要更多的维护.

这是最简单的方式还是类似于DateDecodingStrategy用于其他类型的转换?

更新:我应该注意:我也走了超越的路线

init(from decoder:Decoder)
Run Code Online (Sandbox Code Playgroud)

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

Ham*_*ish 18

不幸的是,我不相信当前的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)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以拥有这种类型的属性并使用自动生成的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"
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然不幸的是,现在你必须谈论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"
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然这仍然不像它应该的那样光滑 - 希望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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @LordAndrei我建议在[swift evolution邮件列表](https://lists.swift.org/mailman/listinfo/swift-evolution)上提出它.我最初的感觉是,将它作为`JSONDecoder` /`JSONEncoder`的额外选项会更好,而不是作为`Codable`的大修.鉴于现有的解码和编码异常浮点值到字符串的选项,它似乎是一个自然的去处. (3认同)

Rob*_*Rob 14

您始终可以手动解码.所以,给定:

{
    "name": "Bob",
    "age": 25,
    "tax_rate": "4.25"
}
Run Code Online (Sandbox Code Playgroud)

你可以做:

struct Example: Codable {
    let name: String
    let age: Int
    let taxRate: Float

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        age = try values.decode(Int.self, forKey: .age)
        guard let rate = try Float(values.decode(String.self, forKey: .taxRate)) else {
            throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.taxRate], debugDescription: "Expecting string representation of Float"))
        }
        taxRate = rate
    }

    enum CodingKeys: String, CodingKey {
        case name, age
        case taxRate = "tax_rate"
    }
}
Run Code Online (Sandbox Code Playgroud)

编码和解码手动编码和解码自定义类型.

但我同意,似乎应该有一个更优雅的字符串转换过程相当于DateDecodingStrategy给出了多少个JSON源,错误地将数值作为字符串返回.


Ima*_*tit 8

根据您的需要,您可以选择以下两种方法之一来解决您的问题.


#1.使用Decodable init(from:)初始化程序

当你需要从转换使用此策略StringFloat为单一结构,枚举或类.

import Foundation

struct ExampleJson: Decodable {

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

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

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

        name = try container.decode(String.self, forKey: CodingKeys.name)
        age = try container.decode(Int.self, forKey: CodingKeys.age)
        let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate)
        guard let taxRateFloat = Float(taxRateString) else {
            let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object")
            throw DecodingError.dataCorrupted(context)
        }
        taxRate = taxRateFloat
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

import Foundation

let jsonString = """
{
  "name": "Bob",
  "age": 25,
  "tax_rate": "4.25"
}
"""

let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
 prints:
 ? __lldb_expr_126.ExampleJson
   - name: "Bob"
   - age: 25
   - taxRate: 4.25
 */
Run Code Online (Sandbox Code Playgroud)

#2.使用中间模型

如果JSON中有许多嵌套键,或者需要从JSON 转换许多键(例如从中转换StringFloat),请使用此策略.

import Foundation

fileprivate struct PrivateExampleJson: Decodable {

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

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

}

struct ExampleJson: Decodable {

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

    init(from decoder: Decoder) throws {
        let privateExampleJson = try PrivateExampleJson(from: decoder)

        name = privateExampleJson.name
        age = privateExampleJson.age
        guard let convertedTaxRate = Float(privateExampleJson.taxRate) else {
            let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object")
            throw DecodingError.dataCorrupted(context)
        }
        taxRate = convertedTaxRate
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

import Foundation

let jsonString = """
{
  "name": "Bob",
  "age": 25,
  "tax_rate": "4.25"
}
"""

let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
 prints:
 ? __lldb_expr_126.ExampleJson
   - name: "Bob"
   - age: 25
   - taxRate: 4.25
 */
Run Code Online (Sandbox Code Playgroud)