Using Decodable in Swift 4 with Inheritance

此生再无相见时 提交于 2019-12-17 02:29:02

问题


Should the use of class inheritance break the Decodability of class. For example, the following code

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

output is:

1
name is nil

Now if I reverse this, name decodes but id does not.

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")

output is:

id is nil
Large Building Development

And you can't express Codable in both classes.


回答1:


I believe in the case of inheritance you must implement Coding yourself. That is, you must specify CodingKeys and implement init(from:) and encode(to:) in both superclass and subclass. Per the WWDC video (around 49:28, pictured below), you must call super with the super encoder/decoder.

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)

}

The video seems to stop short of showing the encoding side (but it's container.superEncoder() for the encode(to:) side) but it works in much the same way in your encode(to:) implementation. I can confirm this works in this simple case (see playground code below).

I'm still struggling with some odd behavior myself with a much more complex model I'm converting from NSCoding, which has lots of newly-nested types (including struct and enum) that's exhibiting this unexpected nil behavior and "shouldn't be". Just be aware there may be edge cases that involve nested types.

Edit: Nested types seem to work fine in my test playground; I now suspect something wrong with self-referencing classes (think children of tree nodes) with a collection of itself that also contains instances of that class' various subclasses. A test of a simple self-referencing class decodes fine (that is, no subclasses) so I'm now focusing my efforts on why the subclasses case fails.

Update June 25 '17: I ended up filing a bug with Apple about this. rdar://32911973 - Unfortunately an encode/decode cycle of an array of Superclass that contains Subclass: Superclass elements will result in all elements in the array being decoded as Superclass (the subclass' init(from:) is never called, resulting in data loss or worse).

//: 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)

Both the super- and subclass properties are restored in fullSubDecoded.




回答2:


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)
}

For Decoding I did this:

 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

}



回答3:


I was able to make it work by making my base class and subclasses conform to Decodable instead of Codable. If I used Codable it would crash in odd ways, such as getting a EXC_BAD_ACCESS when accessing a field of the subclass, yet the debugger could display all the subclass values with no problem.

Additionally, passing the superDecoder to the base class in super.init() didn't work. I just passed the decoder from the subclass to the base class.




回答4:


How about using the following way?

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"
    }
}

Additional info on composition: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/




回答5:


Here is a library TypePreservingCodingAdapter to do just that (can be installed with Cocoapods or SwiftPackageManager).

The code below compiles and works just fine with Swift 4.2. Unfortunately for every subclass you'll need to implement encoding and decoding of properties on your own.

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


来源:https://stackoverflow.com/questions/44553934/using-decodable-in-swift-4-with-inheritance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!