问题描述
问题:解码属于Parent和Child类的对象数组。
我在这个问题上读了很多东西,但是我找不到一个简单的解决方案。
我对类型属性进行了编码,该属性提供了原始类的信息,但是我还没有找到在解码对象时使用它的方法。
class Parent: Codable,CustomDebugStringConvertible {
var debugDescription: String {
return "[\(name)]"
}
var name: String
init(name: String) {
self.name = name
}
enum CodingKeys: CodingKey {
case name
case type
case age
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try! container.decode(String.self,forKey: .name)
let type = try! container.decode(String.self,forKey: .type)
print("Reading \(type)")
if type == "Child" {
try Child.init(from: decoder)
return
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("Parent",forKey: .type)
try container.encode(name,forKey: .name)
}
}
class Child: Parent {
override var debugDescription: String {
return "[\(name) - \(age)]"
}
var age: Int
init(name: String,age: Int) {
self.age = age
super.init(name: name)
}
enum CodingKeys: CodingKey {
case name
case age
case type
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try! container.decode(Int.self,forKey: .age)
let name = try! container.decode(String.self,forKey: .name)
super.init(name: name) // I think the problem is here!
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode("Child",forKey: .type)
try container.encode(age,forKey: .age)
}
}
这是测试代码。
let array = [Parent(name: "p"),Child(name: "c",age: 2)]
print(array)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
do {
let jsonData = try encoder.encode(array)
let s = String(data: jsonData,encoding: .ascii)
print("Json Data \(s!)")
let decodedArray = try decoder.decode([Parent].self,from: jsonData)
print(decodedArray)
}
catch {
print(error.localizedDescription)
}
原始数组的输出为:
[[p],[c - 2]]
解码数组的输出为:
[[p],[c]]
如何更改父级和/或子级init函数以正确解码对象?
很明显,我的实际情况要复杂得多。我必须对包含继承类的数组的类进行编码/解码。我试图用这个:
https://github.com/IgorMuzyka/Type-Preserving-Coding-Adapter
显然,它可以在Parent,Child数组上正常工作,但是如果该数组在另一个类中则不能。
此外,我想学习一种在其他情况下可以重用的解决方案,避免严格要求不包含外部库。
解决方法
我认为问题的主要部分是,您正在使用混合类型数组[Any],然后将其解码为一种类型Parent,因为很有可能获得正确编码的子对象小时候。
一种解决方案是创建一个新的Codable结构,该结构保存数组,并使用 type 属性跟踪如何解码数组中的对象
enum ObjectType: String,Codable {
case parent
case child
}
struct ParentAndChild: Codable {
let objects: [Parent]
enum CodingKeys: CodingKey {
case objects
}
enum ObjectTypeKey: CodingKey {
case type
}
init(with objects: [Parent]) {
self.objects = objects
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var objectsArray = try container.nestedUnkeyedContainer(forKey: CodingKeys.objects)
var items = [Parent]()
var array = objectsArray
while !objectsArray.isAtEnd {
let object = try objectsArray.nestedContainer(keyedBy: ObjectTypeKey.self)
let type = try object.decode(ObjectType.self,forKey: ObjectTypeKey.type)
switch type {
case .parent:
items.append(try array.decode(Parent.self))
case .child:
items.append(try array.decode(Child.self))
}
}
self.objects = items
}
}
我也对类进行了一些更改,极大简化了Parent类,Child类修改了编码/解码功能,其中主要更改是init(from:)
调用了超级init(from:)
class Parent: Codable,CustomDebugStringConvertible {
var debugDescription: String {
return "[\(name)]"
}
var name: String
init(name: String) {
self.name = name
}
}
class Child: Parent {
override var debugDescription: String {
return "[\(name) - \(age)]"
}
var age: Int
init(name: String,age: Int) {
self.age = age
super.init(name: name)
}
enum CodingKeys: CodingKey {
case age
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try container.decode(Int.self,forKey: .age)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(age,forKey: .age)
}
}