Полиморфное декодирование подобъекта KeyedDecodingContainer в Swift - PullRequest
0 голосов
/ 27 февраля 2020

Следующий код пытается создать тип Codable AnyBase, который позволит вам кодировать и декодировать различные подклассы Base. Как написано, in терпит неудачу, потому что он пытается искать объекты типа по строковому коду, используя getTypeFromCode. Но если вы используете закомментированную часть вместо этого с жестко закодированными типами, она печатает «Sub1» по желанию.

Есть ли способ настроить это, чтобы использовать что-то вроде getTypeFromCode и устранить жестко закодированные типы от init?

class Base: Codable {
    var foo: Int = 1
    var codingTypeKey: String { fatalError("abstract") }
}

class Sub1: Base {
    var sub1Prop: Int = 2
    override var codingTypeKey: String { "sub1" }
    enum CodingKeys: CodingKey {
        case sub1Prop
    }
    override init() {
        super.init()
    }
    required init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        sub1Prop = try c.decode(Int.self, forKey: .sub1Prop)
        try super.init(from: decoder)
    }
    override func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        try c.encode(sub1Prop, forKey: .sub1Prop)
        try super.encode(to: encoder)
    }
}
class Sub2: Base {
    var sub2Prop: Int = 2
    override var codingTypeKey: String { "sub2" }
}

func getTypeFromCode(_ typeCode: String) -> Base.Type {
    if typeCode == "sub1" { return Sub1.self }
    else if typeCode == "sub2" { return Sub2.self }
    else { fatalError("bad type code") }
}

class AnyBase: Codable {
    let base: Base
    init(_ b: Base) { base = b }
    required init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let typeCode = try c.decode(String.self, forKey: .type)

        /*if typeCode == "sub1" {
            self.base = try c.decode(Sub1.self, forKey: .base)
        } else if typeCode == "sub2" {
            self.base = try c.decode(Sub2.self, forKey: .base)
        } else {
            fatalError("bad type code")
        }*/

        let typeObj = getTypeFromCode(typeCode)
        self.base = try c.decode(typeObj, forKey: .base)
    }
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        try c.encode(base.codingTypeKey, forKey: .type)
        try c.encode(base, forKey: .base)
    }

    enum CodingKeys: CodingKey {
        case base, type
    }
}

// To to round-trip encode/decode and get the right object back...
let enc = JSONEncoder()
let dec = JSONDecoder()
let sub = Sub1()
let any = AnyBase(sub)
let data = try! enc.encode(any)
let str = String(data:data, encoding:.utf8)!
print(str)
let any2 = try! dec.decode(AnyBase.self, from: data)
print(type(of:any2.base))

1 Ответ

0 голосов
/ 27 февраля 2020

Вместо метода getTypeFromCode (_:) вы можете создать enum BaseType как,

enum BaseType: String {
    case sub1, sub2

    var type: Base.Type {
        switch self {
        case .sub1: return Sub1.self
        case .sub2: return Sub2.self
        }
    }
}

Теперь, в init(from:) получите BaseType, используя typeCode как rawValue, т.е. 1010 *

class AnyBase: Codable {
    required init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let typeCode = try c.decode(String.self, forKey: .type)
        if let baseType = BaseType(rawValue: typeCode) {
            self.base = try c.decode(baseType.type, forKey: .base)
        } else {
            fatalError("bad type code")
        }
    }
    //rest of the code...
}
...