Как создать объект из JSON в Swift? - PullRequest
0 голосов
/ 01 февраля 2020

У меня есть N классов, все производные от Base:

class Base {...}

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

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

Они были сериализованы в файл json, который содержит массив Base. Это выглядит так:

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

Когда я декодирую json, у меня появляется словарь, подобный одному из 2 выше. Как я могу затем создать из него объект одного из моих N классов?

Важное примечание: по разным причинам я не могу использовать Codable и JSONEncoder, JSONDecoder

Ответы [ 2 ]

2 голосов
/ 01 февраля 2020

Поскольку JSON содержит тип, используйте его для определения различных классов

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
        }
    }
}
1 голос
/ 03 февраля 2020

Для классов это не так просто, как для структур, но реализовать этот тип 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

Структуры немного проще, и вы можете избавиться от оператора switch , С классами это сделать сложнее, потому что он вводит необходимые начальные элементы, которые утомительно реализовывать.

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