在Swift 4中使用Decodable继承

Kev*_*own 59 swift swift4 codable

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

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
Run Code Online (Sandbox Code Playgroud)

输出是:

1
name is nil
Run Code Online (Sandbox Code Playgroud)

现在,如果我反转这个,名称解码但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")
Run Code Online (Sandbox Code Playgroud)

输出是:

id is nil
Large Building Development
Run Code Online (Sandbox Code Playgroud)

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

Jos*_*zzi 65

我相信继承的情况你必须Coding自己实现.也就是说,你必须指定CodingKeys和执行init(from:),并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)

}
Run Code Online (Sandbox Code Playgroud)

视频似乎停止短显示编码端的(但它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)
Run Code Online (Sandbox Code Playgroud)

超级和子类属性都将恢复fullSubDecoded.

  • 通过将基类转换为协议并将默认实现添加到协议扩展并使派生类符合它,能够解决此问题 (3认同)
  • 我运行代码swift 4.1.我在使用superDecoder时遇到异常.并使用`super.init(来自:decoder)`正常工作 (3认同)
  • 实际上,不需要`container.superDecoder()`。super.init(from:解码器)就足够了 (2认同)
  • `try super.encode(to:container.superEncoder())` 在编码时添加了一个超级键 (2认同)

dev*_*jme 11

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

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)
}
Run Code Online (Sandbox Code Playgroud)

对于解码,我这样做了:

 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

}
Run Code Online (Sandbox Code Playgroud)

  • 不错的博文!谢谢你的分享。 (2认同)

Dej*_*dar 6

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
}
Run Code Online (Sandbox Code Playgroud)

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

可在GitHub (SerializedSwift) 上获得


小智 5

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

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


Nav*_*Nav 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"
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这如何解决异构数组的解码问题? (4认同)
  • 这不是构图。 (4认同)
  • 需要澄清的是,这并不是尖锐的批评。我不断重新审视存储异构集合的问题,但无济于事。通用解决方案是最好的,这意味着我们在解码时无法知道类型。 (2认同)

Igo*_*yka 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
Run Code Online (Sandbox Code Playgroud)