在Swift 4中使用Decodable继承

问题描述 投票:49回答:5

是否应该使用类继承来破坏类的可解码性。例如,以下代码

class Server : Codable {
    var id : Int?
}

class Development : Server {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here

输出是:

1
name is nil

现在,如果我反转这个,名称解码但id没有。

class Server {
    var id : Int?
}

class Development : Server, Codable {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil")

输出是:

id is nil
Large Building Development

并且你不能在两个班级中表达Codable。

swift swift4 codable
5个回答
61
投票

我相信继承的情况你必须自己实现Coding。也就是说,您必须指定CodingKeys并在超类和子类中实现init(from:)encode(to:)。根据WWDC video(大约49:28,如下图所示),您必须使用超级编码器/解码器调用super。

WWDC 2017 Session 212 Screenshot at 49:28 (Source Code)

required init(from decoder: Decoder) throws {

  // Get our container for this subclass' coding keys
  let container = try decoder.container(keyedBy: CodingKeys.self)
  myVar = try container.decode(MyType.self, forKey: .myVar)
  // otherVar = ...

  // Get superDecoder for superclass and call super.init(from:) with it
  let superDecoder = try container.superDecoder()
  try super.init(from: superDecoder)

}

该视频似乎没有显示编码方面(但它是container.superEncoder()方面的encode(to:))但它在encode(to:)实现中的工作原理大致相同。我可以在这个简单的例子中确认这是有效的(参见下面的游乐场代码)。

我仍在努力解决一些奇怪的行为,我正在使用一个更加复杂的模型,我正在从NSCoding转换,它有许多新嵌套类型(包括structenum),这些类型表现出这种意想不到的nil行为并且“不应该” 。请注意,可能存在涉及嵌套类型的边缘情况。

编辑:嵌套类型似乎在我的测试操场中正常工作;我现在怀疑自引用类(想想树节点的子节点)有一个错误,它有一个自身的集合,它也包含该类的各个子类的实例。对一个简单的自引用类的测试解码很好(也就是说,没有子类)所以我现在正集中精力研究子类案例失败的原因。

更新于2017年6月25日:我最终向Apple提交了一个关于此问题的错误。 rdar:// 32911973 - 不幸的是,包含Superclass元素的Subclass: Superclass数组的编码/解码周期将导致数组中的所有元素被解码为Superclass(从不调用子类'init(from:),导致数据丢失或更糟)。

//: Fully-Implemented Inheritance

class FullSuper: Codable {

    var id: UUID?

    init() {}

    private enum CodingKeys: String, CodingKey { case id }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)

    }

}

class FullSub: FullSuper {

    var string: String?
    private enum CodingKeys: String, CodingKey { case string }

    override init() { super.init() }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let superdecoder = try container.superDecoder()
        try super.init(from: superdecoder)

        string = try container.decode(String.self, forKey: .string)

    }

    override func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(string, forKey: .string)

        let superencoder = container.superEncoder()
        try super.encode(to: superencoder)

    }
}

let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"

let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)

let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)

超级和子类属性都在fullSubDecoded中恢复。


10
投票

Found This Link - Go down to inheritance section

override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(employeeID, forKey: .employeeID)
}

对于解码,我这样做了:

 required init(from decoder: Decoder) throws {

    try super.init(from: decoder)

    let values = try decoder.container(keyedBy: CodingKeys.self)
    total = try values.decode(Int.self, forKey: .total)
  }

private enum CodingKeys: String, CodingKey
{
    case total

}

4
投票

通过使我的基类和子类符合Decodable而不是Codable,我能够使它工作。如果我使用Codable它会以奇怪的方式崩溃,例如在访问子类的字段时获取EXC_BAD_ACCESS,但调试器可以显示所有子类值没有问题。

另外,将superDecoder传递给super.init()中的基类不起作用。我只是将解码器从子类传递给基类。


4
投票

使用以下方式怎么样?

protocol Parent: Codable {
    var inheritedProp: Int? {get set}
}

struct Child: Parent {
    var inheritedProp: Int?
    var title: String?

    enum CodingKeys: String, CodingKey {
        case inheritedProp = "inherited_prop"
        case title = "short_title"
    }
}

关于组成的其他信息:http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/


1
投票

这是一个库TypePreservingCodingAdapter就是这样做的(可以用Cocoapods或SwiftPackageManager安装)。

下面的代码编译并与Swift 4.2一起使用。不幸的是,对于每个子类,您需要自己实现属性的编码和解码。

import TypePreservingCodingAdapter
import Foundation

// redeclared your types with initializers
class Server: Codable {
    var id: Int?

    init(id: Int?) {
        self.id = id
    }
}

class Development: Server {
    var name: String?
    var userId: Int?

    private enum CodingKeys: String, CodingKey {
        case name
        case userId
    }

    init(id: Int?, name: String?, userId: Int?) {
        self.name = name
        self.userId = userId
        super.init(id: id)
    }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)

        name = try container.decodeIfPresent(String.self, forKey: .name)
        userId = try container.decodeIfPresent(Int.self, forKey: .userId)
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(userId, forKey: .userId)
    }

}

// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()

// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter

// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)


let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)

let servers: [Server] = [server, development]

// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })

// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }

// check that decoded object are of correct types
print(decodedServers.first is Server)     // prints true
print(decodedServers.last is Development) // prints true
© www.soinside.com 2019 - 2024. All rights reserved.