这一节,我们来看当model存在继承关系的时候,是如何与JSON完成自动转换的,它的默认行为,和我们想象的有点儿不太一样。
假设,我们有下面这两个类:
class Point2D: Codable {
var x = 0.0
var y = 0.0
}
class Point3D: Point2D {
var z = 0.0
}
由于这两个类包含的都是简单属性,我们希望直接让它们遵从Codable
,实现JSON到model的自动转化,但事情却并不如我们想象的这么简单。在接下来的内容里,为了方便观察对象编码的结果,我们定义了一个全局encode
函数,在后面的视频中,我们也会使用它:
func encode<T>(of model: T) throws where T: Codable {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(model)
print(String(data: data, encoding: .utf8)!)
}
这基本就是我们之前手动编码对象的封装,很简单。唯一要注意的就是我们在最后,对泛型参数T
进行了类型约束,要求它遵从Codable
。接下来,我们创建一个Point3D
对象,然后对它编码:
var p1 = Point3D()
p1.x = 1
p1.y = 1
p1.z = 1
encode(of: p1)
这段代码会得到什么结果呢?你可能会想,当然是包含xyz的JSON啊。但执行一下,你会看到这样的结果:
{
"x" : 1,
"y" : 1
}
z
呢?如果你第一次执行这段代码,一定会感到奇怪。默认Codable
中的默认encode
方法并不能正确处理派生类对象。因此,当我们的model是派生类时,要自己编写对应的编码和解码的方法。
先来看编码,我们先实现基类的部分:
class Point2D: Codable {
var x = 0.0
var y = 0.0
private enum CodingKeys: String, CodingKey {
case x
case y
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(x, forKey: .x)
try container.encode(y, forKey: .y)
}
}
唯一需要注意的,就是基类中的CodingKeys
被修饰成了private
。因为派生类中,也要定义它自己的CodingKeys
,我们要避免它被派生类继承。
然后是派生类的部分:
class Point3D: Point2D {
var z = 0.0
private enum CodingKeys: String, CodingKey {
case z
}
override func encode(to encoder: Encoder) throws {
var container =
encoder.container(keyedBy: CodingKeys.self)
try container.encode(z, forKey: .z)
}
}
现在,你应该想:这下总该没问题了吧。但如果执行下,你就会发现这样的结果:
{
"z" : 1
}
这时,也许你马上就意识到问题所在了,在派生类的encode
方法里,先调用基类版本的encode
就应该正确了:
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(z, forKey: .z)
}
重新执行一下,就会得到正确的结果了:
{
"x" : 1,
"y" : 1,
"z" : 1
}
在派生类的实现里,基类和派生类的属性共享了同一个container
。但这样的方式并不利于我们了解其中哪部分属于基类,哪部分属于派生类。为了能在编码后的结果中方便区分,我们得让它们使用不同的容器:
override func encode(to encoder: Encoder) throws {
var container =
encoder.container(keyedBy: CodingKeys.self)
try super.encode(to: container.superEncoder())
try container.encode(z, forKey: .z)
}
这里,我们使用了container.superEncoder()
获得了派生类中,专门用于编码基类的容器,并把它传递给了基类的编码方法。现在,重新执行一下,我们会看到下面这样的结果:
{
"super" : {
"x" : 1,
"y" : 1
},
"z" : 1
}
其中,基类和派生类的部分就很清楚了。但我们还可以进一步改进这个结果,把派生类中的CodingKeys
改成这样:
private enum CodingKeys: String, CodingKey {
case z
case point_2d
}
然后,在派生类的encode
方法里,指定编码基类时的key:
override func encode(to encoder: Encoder) throws {
var container =
encoder.container(keyedBy: CodingKeys.self)
try super.encode(to:
container.superEncoder(forKey: .point_2d))
try container.encode(z, forKey: .z)
}
重新执行下,结果就会这样:
{
"z" : 1,
"point_2d" : {
"x" : 1,
"y" : 1
}
}
理解了编码的过程之后,解码的过程是类似的,无非就是把共享容器,或使用基类独立容器的代码写在init(from decoder: Decoder)
方法里,大家可以试着自己写写,我们就不再重复了。
网友评论