0

I have N classes all deriving from Base :

class Base {...}

class A : Base {
   init(a:Int) {...}
}

class B : Base {
   init(b:Int, c:Int) {...}
}

They have been serialized into a json file that contains an array of Base. It looks like this:

{
"elements" : [
    {
    "type" : "A",
    "a" : 3
    },
    {
    "type" : "B",
    "b" : 30,
    "c" : 45
    }
  ]
}

When I decode the json, I have a dictionary like one of the 2 above. How can I then create an object of one of my N classes from it ?

Important note : for various reasons, I cannot use Codable, and JSONEncoder, JSONDecoder

5
  • So this is what your stackoverflow.com/questions/60019259/… was really about? Isn't this sort of a sneaky repeat of the same question? I mean, if there was a simple way to do the other question, solving this question would follow as the night the day. Commented Feb 1, 2020 at 19:00
  • Could you explain your "various reasons?" Codable would handles this quite naturally. Commented Feb 2, 2020 at 16:06
  • Mainly, I need this in a context with a little complexity with classes (array of classes that inherit from a protocol and a parent class), and was unable to make it work with the encoder. Commented Feb 2, 2020 at 18:54
  • "Unable to make it work with an encoder" is very different than "cannot use." Is the above example a reasonable description of your data? It definitely can be decoded with Decodable, and I recommend that strongly over JSONSerialization or the like. Commented Feb 2, 2020 at 21:54
  • It’s quite different from my data, as I need to give a minimal reproducible example. I tried for a long time to make it work with JSONEncoder with the complex data, and I am unsure whether it strictly cannot be done, or whether it can but it’s very hard. In either case, using JSonSerialization solved the problem eventually for me. Thanks for your thoughts though. Commented Feb 2, 2020 at 21:59

2 Answers 2

2

As the JSON contains the type use it to determine the different classes

if let elements = root["elements"] as? [[String:Any]] {
    for element in elements {
        let type = element["type"] as! String
        switch type {
            case "A": 
              let a = element["a"] as! Int
              let aInstance = A(a: a)
            case "B": // decode b and c and create instance of B
            // and so on
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for that. My issue though is what you left in comments: how to decode a and init it.
1

For classes, it's not quite as straightforward as for structs, but it's still pretty easy to implement this kind of Decodable.

struct ElementContainer: Decodable {
    let elements: [Base]
    enum CodingKeys: String, CodingKey { case elements }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.elements = try container.decode([Element].self, forKey: .elements)
            .map { $0.base }
    }
}

struct Element: Decodable {
    let base: Base
    enum CodingKeys: String, CodingKey {
        case type
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)

        switch type {
        case "A":
            base = try A(from: decoder)
        case "B":
            base = try B(from: decoder)

        default:
            throw DecodingError.dataCorruptedError(forKey: .type,
                                                   in: container,
                                                   debugDescription: "Unknown type: \(type)")
        }
    }
}

class Base {}

class A : Base, Decodable {
    let a: Int
    init(a:Int) {
        self.a = a
        super.init()
    }
}

class B : Base, Decodable {
    let b: Int
    let c: Int
    init(b:Int, c:Int) {
        self.b = b
        self.c = c
        super.init()
    }
}

let results = try JSONDecoder().decode(ElementContainer.self, from: json).elements

Structs are a little simpler and you can get rid of the switch statement. It's harder to do this with classes because it introduces required inits that are tedious to implement.

struct ElementContainer: Decodable {
    let elements: [Element]
}

struct Element: Decodable {
    let base: Base
    enum CodingKeys: String, CodingKey {
        case type
    }

    static let mapping: [String: Base.Type] = ["A": A.self, "B": B.self]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)

        guard let elementType = Element.mapping[type] else {
            throw DecodingError.dataCorruptedError(forKey: .type,
                                                   in: container,
                                                   debugDescription: "Unknown type: \(type)")
        }

        base = try elementType.init(from: decoder)
    }
}

protocol Base: Decodable {}

struct A : Base {
    let a: Int
}

struct B : Base {
    let b: Int
    let c: Int
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.