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

Swift Decodable: how to transform one of values during decoding?

默认情况下,Decodable 协议将 JSON 值转换为对象值,没有任何变化。但有时你需要在 json 解码期间转换值,例如,在 JSON 中你得到 {id = "id10"} 但在你的 class 实例中你需要输入数字 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
}

这对我来说听起来很棒,但是如果我想更改值 只是 JSON 字段中的一个 并且让我的所有其他 20 个字段保持不变怎么办?在 init(from:) 的情况下,我应该为我的 class 的 20 个字段中的每一个字段手动获取和输入值!经过多年的 objC 编码,我很直观地首先调用 super 的 init(from:) 实现,然后仅对某些字段进行更改,但是我如何使用 Swift 和 Decodable 协议实现这样的效果?

您可以使用 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"
    }
}

示例:

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)

目前,如果您想要更改单个 属性.

的解析,则必须完全实现 encodedecode 方法

Swift Codable 的某些未来版本可能允许逐个处理每个 属性 的编码和解码。但是 Swift 功能工作非常重要,尚未确定优先级:

Regardless, the goal is to likely offer a strongly-typed solution that allows you to do this on a case-by-case basis with out falling off the "cliff" into having to implement all of encode(to: and init(from: for the benefit of one property; the solution is likely nontrivial and would require a lot of API discussion to figure out how to do well, hence why we haven't been able to do this yet.

- Itai Ferber, lead developer on Swift 4 Codable

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