如何在Swift中解码一组继承的类 [英] How to decode an array of inherited classes in Swift

查看:51
本文介绍了如何在Swift中解码一组继承的类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:解码属于Parent和Child类的对象数组.

The problem: decode an array of objects belonging to Parent and Child classes.

我在这个问题上读了很多东西,但是我找不到一个简单的解决方案.

I read a lot of stuff on this subject but I have not been able to find a simple solution.

我对类型属性进行了编码,该属性提供了原始类的信息,但是我还没有找到一种在解码对象时使用它的方法.

I encoded a type property which provide the information of the original class, but I haven't found a way to use it in decoding the object.

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函数以正确解码对象?

How do I change the Parent and/or the Child init function in order to correctly decode the object?

很明显,我的实际情况要复杂得多.我必须对包含继承类的数组的类进行编码/解码.我试图用这个:

Clearly, my actual scenario is much more complex of this. I have to encode / decode a class which contains an array of classes with inheritance. I have tried to use this:

https://github.com/IgorMuzyka/Type-Preserving-Coding-Adapter

显然,它可以在Parent,Child数组上正常工作,但是如果该数组在另一个类中则不能.

Apparently, it works fine on an array of Parent, Child but it doesn't if the array is inside another class.

此外,我想学习一种在其他情况下可以重用的解决方案,并避免严格要求不包含外部库.

Moreover, I would like to learn a solution to reuse in other cases and avoid including external library is not strictly needed.

推荐答案

我认为问题的主要部分是您正在使用混合类型数组[Any],然后将其解码为一种类型Parent因为很有可能使子对象正确编码为Child.

I think a major part of the problem is that you are using an array of mixed types, [Any], and then you are decoding it as one type Parent because it is quite possible to get the child objects to be properly encoded as Child.

一种解决方案是创建一个新的Codable结构,该结构保存数组,并使用 type 属性跟踪如何解码数组中的对象

One solution is to create a new Codable struct that holds the array and that with the use of a type property keeps track on how to decode the objects in the array

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:)

I have also made some changes to the classes as well, the Parent class is hugely simplified and the Child class has modified functionality for encoding/decoding where the main change is that init(from:) calls supers 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)
    }
}

这篇关于如何在Swift中解码一组继承的类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆