在Swift 4中通过继承使用Decodable

roejwanj  于 2023-09-30  发布在  Swift
关注(0)|答案(7)|浏览(104)

类继承的使用是否破坏了类的可解码性。例如,下面的代码

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

现在如果我把这个反过来,name可以解码,但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。

goucqfw6

goucqfw61#

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

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)

}

视频似乎没有显示编码端(但它是encode(to:)端的container.superEncoder()),但它在encode(to:)实现中的工作方式大致相同。我可以在这个简单的例子中确认这是可行的(参见下面的playground代码)。
我自己仍然在与一些奇怪的行为作斗争,我正在从NSCoding转换一个更复杂的模型,它有很多新嵌套的类型(包括structenum),这些类型表现出这种意想不到的nil行为和“不应该”。请注意,可能存在涉及嵌套类型的边缘情况。

**编辑:**嵌套类型似乎在我的测试操场上工作得很好;我现在怀疑自引用类(想想树节点的子节点)有什么问题,它的集合本身也包含该类的各种子类的示例。对一个简单的自引用类的测试可以很好地解码(也就是说,没有子类),所以我现在将精力集中在子类失败的原因上。
**更新6月25 '17:**我最终向苹果提交了一个关于这个问题的bug。rdar://32911973 -不幸的是,包含Subclass: Superclass元素的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中,超级和子类属性都被恢复。

ia2d9nvy

ia2d9nvy2#

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

}
mwyxok5s

mwyxok5s3#

Swift在5.1中引入了Property Wrappers,我实现了一个名为**SerializedSwift的库,它使用了property wrappers的功能来将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)

dfddblmv

dfddblmv4#

我可以通过使我的基类和子类符合Decodable而不是Codable来使它工作。如果我使用Codable,它会以奇怪的方式崩溃,例如在访问子类的字段时得到EXC_BAD_ACCESS,但调试器可以显示所有子类值而没有问题。
此外,将superDecoder传递给super.init()中的基类也不起作用。我只是把解码器从子类传递到基类。

41zrol4v

41zrol4v5#

用下面的方法怎么样?

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/

0yg35tkg

0yg35tkg6#

这里有一个库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
mnowg1ta

mnowg1ta7#

  • Swift 5酒店**

编译器仅为直接采用Codable协议的类型合成可解码代码,以便您在继承中观察单个类型的解码。
但是你可以用KeyValueCoding包(https://github.com/ikhvorost/KeyValueCoding)尝试下一种通用方法,这个包提供了对所有属性元数据的访问,并允许动态地获取/设置纯swift类型的任何属性。我们的想法是创建一个基本的Coding类,它采用KeyValueCoding并实现对init(from: Decoder)中所有可用属性的解码:

class Coding: KeyValueCoding, Decodable {
    
    typealias DecodeFunc = (KeyedDecodingContainer<_CodingKey>, _CodingKey) throws -> Any?
    
    struct _CodingKey: CodingKey {
      let stringValue: String
      let intValue: Int?

      init(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = Int(stringValue)
      }

      init(intValue: Int) {
        self.stringValue = "\(intValue)"
        self.intValue = intValue
      }
    }
    
    static func decodeType<T: Decodable>(_: T.Type) -> (type: T.Type, f: DecodeFunc)  {
        (T.self,  { try $0.decode(T.self, forKey: $1) })
    }
    
    static var decodeTypes: [(Any.Type, DecodeFunc)] = [
        decodeType(Int.self),
        decodeType(Int?.self),
        decodeType(String.self),
        decodeType(String?.self),
        // Other types to support...
    ]
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: _CodingKey.self)
        try container.allKeys.forEach { codingKey in
            let key = codingKey.stringValue
            guard let property = (properties.first { $0.name == key }),
                let item = (Self.decodeTypes.first { property.type == $0.0 })
            else {
                return
            }
            var this = self
            this[key] = try item.1(container, codingKey)
        }
    }
}

decodeTypes变量中提供所有支持的类型进行解码是很重要的。
使用方法:

class Server: Coding {
    var id: Int?
}

class Development : Server {
    var name: String = ""
}

class User: Development {
    var userId: Int = 0
}

func decode() {
    let json = "{\"id\": 1, \"name\": \"Large Building Development\", \"userId\": 123}"
    do {
        let user = try JSONDecoder().decode(User.self, from:json.data(using: .utf8)!)
        print(user.id, user.name, user.userId) // Optional(1) Large Building Development 123
    }
    catch {
        print(error.localizedDescription)
    }
}

相关问题