Swift Decodable:如何在解码过程中转换其中一个值?

sur*_*der 7 json decoder ios swift codable

默认情况下,Decodable协议将JSON值转换为对象值而不进行任何更改.但有时您需要在json解码期间转换值,例如,在JSON中,{id = "id10"}但是在您的类实例中,您需要将数字10放入属性id(或者使用不同名称的偶数属性).

您可以init(from:)使用任何值实现可以执行所需操作的方法,例如:

public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    latitude = try container.decode(Double.self, forKey:.latitude)
    longitude = try container.decode(Double.self, forKey: .longitude)
    // and for example there i can transform string "id10" to number 10
    // and put it into desired field
}
Run Code Online (Sandbox Code Playgroud)

这听起来对我来说很棒,但是如果我只想为其中一个 JSON字段更改值并将所有其他20个字段保留为没有变化,该怎么办?如果init(from:)我应该手动获取并为我班级的20个字段中的每个字段添加值!经过多年的objC编码,我直接调用super的实现,init(from:)然后对某些字段进行更改,但是如何使用Swift和Decodable协议实现这样的效果呢?

Cod*_*ent 5

您可以使用一个lazy var. 缺点是您仍然必须提供键列表,并且不能将模型声明为常量:

struct MyModel: Decodable {
    lazy var id: Int = {
        return Int(_id.replacingOccurrences(of: "id", with: ""))!
    }()
    private var _id: String

    var latitude: CGFloat
    var longitude: CGFloat

    enum CodingKeys: String, CodingKey {
        case latitude, longitude
        case _id = "id"
    }
}
Run Code Online (Sandbox Code Playgroud)

例子:

let json = """
{
    "id": "id10",
    "latitude": 1,
    "longitude": 1
}
""".data(using: .utf8)!

// Can't use a `let` here
var m = try JSONDecoder().decode(MyModel.self, from: json)
print(m.id)
Run Code Online (Sandbox Code Playgroud)


pka*_*amb 2

目前,如果您想更改单个属性的解析,则必须完全实现encode和方法。decode

Swift Codable 的某些未来版本可能会允许逐案处理每个属性的编码和解码。但 Swift 功能的工作并不简单,而且还没有被优先考虑:

无论如何,我们的目标可能是提供一个强类型的解决方案,使您能够根据具体情况执行此操作,而不会掉入“悬崖”,必须实现所有属性encode(to:init(from:为了一个属性的利益而实施;该解决方案可能并不简单,需要进行大量 API 讨论才能弄清楚如何做得好,这就是为什么我们还无法做到这一点。

- Itai Ferber,Swift 4 Codable 首席开发人员

https://bugs.swift.org/browse/SR-5249?focusedCommentId=32638