Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
625 views
in Technique[技术] by (71.8m points)

swift4 - Using Decodable in Swift 4 with Inheritance

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.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

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)

}

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...