90

如果使用类继承会破坏类的可解码性。例如下面的代码

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 。

4

6 回答 6

103

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

WWDC 2017 Session 212 49:28 截图(源码)

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 提交了一个关于此的错误。Superclassrdar://32911973 - 不幸的是,包含元素的数组的编码/解码循环Subclass: Superclass将导致数组中的所有元素都被解码为Superclassinit(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

于 2017-06-17T14:21:35.650 回答
27

找到此链接 - 转到继承部分

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

}
于 2018-01-30T13:52:00.703 回答
7

Swift 在 5.1 中引入了 Property Wrappers 我实现了一个名为SerializedSwift的库,它使用属性包装器的强大功能将 JSON 数据解码和编码为对象。

我的主要目标之一是,让继承的对象开箱即用解码,无需额外的init(from decoder: Decoder)覆盖。

import SerializedSwift

class User: Serializable {

    @Serialized
    var name: String
    
    @Serialized("globalId")
    var id: String?
    
    @Serialized(alternateKey: "mobileNumber")
    var phoneNumber: String?
    
    @Serialized(default: 0)
    var score: Int
    
    required init() {}
}

// Inherited object
class PowerUser: User {
    @Serialized
    var powerName: String?

    @Serialized(default: 0)
    var credit: Int
}

它还支持自定义编码键、备用键、默认值、自定义转换类以及将来包含的更多功能。

GitHub (SerializedSwift)上可用。

于 2020-09-14T19:53:21.850 回答
5

使用以下方式怎么样?

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/

于 2017-10-14T16:49:54.210 回答
5

这是一个库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
于 2019-01-30T16:05:00.950 回答
5

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

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

于 2017-10-05T21:07:50.940 回答