0
votes

I am very new to Swift and I am trying to create a tree of classes to model my data. I want to use JSONEncoder and JSONDecoder to send and receive objects. I have an issue when decoding generic classes inside another object (nested) because in the init(from: decoder) method I do not have access to other properties that could help me.

In my code:

NestedSecondObject extends NestedObjects, which extends Codable - NestedObject can be extended by NesteThirtObject and so on...

Contact extends Object1, which extends Codable; Contact contains a NestedObject type (which can be any subclass of NestedObject at runtime)

Because JSONEncoder and JSONDecoder do not support inheritance by default, i override the methods "encode" and init(from: decoder) as described here: Using Decodable in Swift 4 with Inheritance

My code is:

class NestedSecondObject: NestedObject {
    var subfield2: Int?

    private enum CodingKeys : String, CodingKey {
        case subfield2
    }

    override init() { super.init() }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(subfield2, forKey: .subfield2)
    }

    required init(from decoder: Decoder) throws
    {
        try super.init(from: decoder)
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.subfield2 = try values.decode(Int.self, forKey: .subfield2)

    }

}


class Contact:Object1 {
    var name: String = ""
    var age: Int = 0
    var address: String = ""
//    var className = "biz.ebas.platform.generic.shared.EContactModel"
    var nestedObject:NestedObject?

    private enum CodingKeys : String, CodingKey {
        case name,age,address,nestedObject
    }

    override init() { super.init() }

    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(age, forKey: .age)
        try container.encode(address, forKey: .address)
        try container.encode(nestedObject, forKey: .nestedObject)
    }

    required init(from decoder: Decoder) throws
    {
        try super.init(from: decoder)
        print(type(of: self))
        print(type(of: decoder))

        let values = try decoder.container(keyedBy: CodingKeys.self)
        print(type(of: values))


        self.name = try values.decode(String.self, forKey: .name)
        self.age = try values.decode(Int.self, forKey: .age)
        self.address = try values.decode(String.self, forKey: .address)
        self.nestedObject = try values.decodeIfPresent(???.self, forKey: .nestedObject)  // HERE i need to know what ???.self is
    }
}

Decoding is:

let jsonDec = JSONDecoder()
let jsonData = json?.data(using: .utf8)
let decodedContact: Contact = try jsonDec.decode(Contact.self, from: jsonData!)

So, basically, when I make a request to the server, I know what types I receive (let's say I request NestedSecondObject), but how do I pass it to the method "init(from decoder:Decoder)" ?

I tried to extend class JSONDecoder and add a simple property like this:

class EJSONDecoder: JSONDecoder {

    var classType:AnyObject.Type?

}

But inside the required init(from decoder:Decoder) method, the type of decoder is not EJSONDecoder, is _JSONDecoder, so I cannot access the decoder.classType property.

Can anyone help with a solution or some sort of workaround? Thanks!

1
Generally I'd say you have to inspect the "raw" JSON data of the nested object to figure out what type it is, then use this type for decoding. - dr_barto

1 Answers

1
votes

New answer

You can give the decoder a userInfo array where you can store things you want to use during decoding.

let decoder = JSONDecoder()
decoder.userInfo = [.type: type(of: NestedSecondObject.self)]
let decodedContact: Contact = try! decoder.decode(Contact.self, from: json)

extension CodingUserInfoKey {
    static let type = CodingUserInfoKey(rawValue: "Type")!
}

Then use it during decoding:

switch decoder.userInfo[.type]! {
case let second as NestedSecondObject.Type:
    self.nestedObject = try values.decode(second, forKey: .nestedObject)
case let first as NestedObject.Type:
    self.nestedObject = try values.decode(first, forKey: .nestedObject)
default:
    fatalError("didnt work")
}

I have sadly not found a way to skip the switch.

Old answer:

Decode as

NestedSecondObject.self

and if that fails decode inside the catch with

NestedObject.self

. Do not catch the NestedObject-decoding cause you want to fail if its not even decodable to the basic type.