Codable KeyNotFound in base class issue

Hi.


I've moving over a small project to use Codable. But unfortunatley I'm running into an issue with saving keys in the base class?


Any clues to what I'm doing wrong. It's hopefully something obvious as I've only just started using it.


Heres the error..

keyNotFound(__lldb_expr_77.base.(CodingKeys in _DFBA3185686E54C6A13CADA5FE9CB15B).uuid, Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key uuid (\"uuid\").", underlyingError: nil))


Heres a simple playground example to demonstrate the issue and which throws the previous error..


class base:Codable{

var uuid = "NONE"

init(){

uuid = UUID().uuidString

}

private enum CodingKeys: String, CodingKey {

case uuid

}

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

self.uuid = try container.decode(String.self, forKey: .uuid)

}

func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(self.uuid, forKey: .uuid)

}

}

class middle:base{

var number:Int

override init(){

self.number = 2

super.init()

}

private enum CodingKeys: String, CodingKey {

case number

}

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

self.number = try container.decode(Int.self, forKey: .number)

let superdecoder = try container.superDecoder()

try super.init(from: superdecoder)

}

override func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(self.number, forKey: .number)

let superdecoder = container.superEncoder()

try super.encode(to: superdecoder)

}

}

let test = middle()

if let jsonData = try? JSONEncoder().encode(test) {

if let jsonString = String(data: jsonData, encoding: .utf8){

let data = jsonString.data(using: .utf8)

do {

let obj = try JSONDecoder().decode(base.self, from: data!)

print (obj)

} catch {

print("\(error)")

}

}

}


Thanks

Replies

If you print "jsonString" just before decoding:


{"number":2,"super":{"uuid":"547647F2-663B-4B73-9A19-C84BA4675D87"}}


you'll see why it doesn't work. The superclass properties are encoded in their own container. That means you can't decode a "middle" object as a "base" object.


Another way of thinking about this is that for the purposes of Codable, a class is not an "amalgam" of its own properties and the base class, even though that's how we use it.


If you must decode as a "base" object, you're going to have to customize the "middle" encoding so that it doesn't "super.encode", but explicitly encode "uuid" as part of its regular encode. Or, perhaps it will work if you "super.encode(to: encoder)", and don't use superEncoder() at all, but this is a bit risky, since you can't be sure of avoiding key clashes if the classes get more complex in the future.

Actually, after consulting the Swift Evolution proposal for Codable:


github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md


I'd say that using "super.encode(to: encoder)" to sort of merge the keys of the two classes is not abnormal (it's closer to what NSKeyedArchiver does), you do have to manage key clashes yourself. You can read about this in the section titled "Inheritance", about half-way down the proposal.

Thank you for the reply. I was using a base class in an array originally. When I made this test playground I didnt make it clear.


Thats cleared a lot of things up though.


Thanks again.