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)
目前,如果您想要更改单个 属性.
的解析,则必须完全实现 encode
和 decode
方法
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
默认情况下,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)
目前,如果您想要更改单个 属性.
的解析,则必须完全实现encode
和 decode
方法
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:
andinit(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