如何在Swift中解码一个继承类的数组

4
问题:解码属于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数组上运行良好,但如果数组位于另一个类中,则无法正常工作。 此外,我想学习一种解决方案,以便在其他情况下重用并避免包含不必要的外部库。

我在这段代码中没有看到你设置Child的age变量的地方。在调用super.init(name: name)之后,我认为你应该调用self.init(name: name, age: age)。你还应该删除self.init中的name参数,因为它已经在超类初始化器中设置了。 - andrija
在super.init(name: name)之后添加self.init(name: name, age: age)会出现以下错误:Initializer cannot both delegate ('self.init') and chain to a superclass initializer ('super.init'). - Fab
好的,那么只需调用 self.init(name: name, age: age),因为你在这个便利初始化器中已经有了 super 调用。 - andrija
1个回答

7
我认为问题的主要部分在于您使用了一个混合类型的数组 [Any],然后将其解码为一个类型 Parent,因为很可能无法将子对象正确编码为 Child。
一种解决方案是创建一个新的 Codable 结构体来保存该数组,并通过使用一个类型属性来跟踪如何解码数组中的对象。
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
    }
}

我还对类进行了一些更改,父类被大幅简化,子类在编码/解码方面具有修改的功能,其中主要变化是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)
    }
}

感谢你的答复。不幸的是,在我的实际代码中,我有大约30个不同的子类,因此按类别拆分数组不是一个选项。 - Fab
@Fab,我已经发布了一个新的解决方案。 - Joakim Danielson
@Fab 为什么不将你的父类转换成一个协议,然后让子类去实现它呢?这是一个正确的做法,而且你可以避免解码问题。 - andrija
我也在研究类似的解决方案,虽然我不太喜欢它,但我认为这是唯一可行的选择。不过,为了使其正常工作,我必须在各自类的编码函数中添加 *** try container.encode(ObjectType.parent, forKey: .type) *** 和 *** try container.encode(ObjectType.child, forKey: .type) ***。答案已被接受,谢谢。 - Fab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接