Swift 4 Декодирование на разные модели из пользовательских объектов JSON - PullRequest
0 голосов
/ 22 января 2019

У меня есть веб-сокет, который генерирует различные объекты json. Объекты не могут содержать общих полей

{
    "type": "apple",
    "kind": "fruit",
    "eatable": true
}
{
    "item": "key",
    "active": true 
}
{
    "tool": "screwdriver",
    "original": "toolBox",
    "cross-head": true
}

У меня есть список классов для них (они могут содержать некоторую логику), поэтому мне нужно проанализировать его, чтобы отобразить некоторые из этих моделей с некоторой иерархической структурой, например. Попытайтесь разобрать фрукты, если они не пройдут, попробуйте проанализировать ключи, если они не получатся, попробуйте разобрать набор инструментов. Иногда мне нужно добавить несколько новых классов для анализа некоторых объектов и некоторых новых полей в существующих классах.
Как организовать сбор комплектации для разбора?

Обновление

  1. У меня нет контроля над внутренними данными, поэтому я не могу добавить поля в JSON, который у меня есть.
  2. Объекты появляются по одному. У меня есть отдельные модели классов для большинства из них. Проблема в том, чтобы выбрать правильный класс для сопоставления полей JSON.

Ответы [ 3 ]

0 голосов
/ 22 января 2019

Вы можете сделать это так:

Сначала вы объявляете свои типы, соответствующие протоколу Decodable:

struct Fruit : Decodable {
    let type : String
    let kind : String
    let eatable : Bool
}

struct Tool : Decodable {
    let tool : String
    let original : String
    let crossHead : Bool

    enum CodingKeys: String, CodingKey {
        case tool = "tool"
        case original = "original"
        case crossHead = "cross-head"
    }
}

Затем вы расширяете Decodable, чтобы «обратить вспять» использование общего вида:

extension Decodable {
    static func decode(data : Data, decoder : JSONDecoder = JSONDecoder()) -> Self? {
        return try? decoder.decode(Self.self, from: data)
    }
}

Затем вы расширяете JSONDecoder, чтобы попробовать декодируемые типы среди тех, которые вы хотите проверить:

extension JSONDecoder {
    func decode(possibleTypes : [Decodable.Type], from data: Data) -> Any? {
        for type in possibleTypes {
            if let value = type.decode(data: data, decoder: self) {
                return value
            }
        }        
        return nil
    }
}

И, в конце концов, вы указываете типы, которые хотите попробовать и декодировать:

let decodableTypes : [Decodable.Type] = [Fruit.self, Tool.self]

Затем вы можете использовать его для декодирования вашего JSON:

let jsonString = """
    {
        "tool": "screwdriver",
        "original": "toolBox",
        "cross-head": true
    }
    """
let jsonData = jsonString.data(using: .utf8)!

let myUnknownObject = JSONDecoder().decode(possibleTypes: decodableTypes, from: jsonData)

И вуаля !!!

Теперь вы можете добавить столько типов, сколько хотите, в ваш decodableTypes, если они соответствуют протоколу Decodable.

Это не лучший подход, потому что если у вас много типов, он не будет оптимальным, но в этом случае вам не нужно добавлять различающее поле в ваши данные.

0 голосов
/ 22 января 2019

Если все эти поля связаны или объединены, вы можете рассмотреть пользователя Enum, который также очень прост в реализации.

    let  data1 = """
    [{
        "type": "apple",
        "kind": "fruit",
        "eatable": true
    },
    {
        "item": "key",
        "active": true
    },
    {
        "tool": "screwdriver",
        "original": "toolBox",
        "cross-head": true
    }]
    """.data(using: .utf8)!

    struct JSONType : Decodable{
        let type: String
        let kind: String
        let eatable : Bool
    }

    struct JSONItem : Decodable{
        let item: String
        let active : Bool
    }

    struct JSONTool : Decodable{
        let tool: String
        let original : String
        let crosshead : Bool

        enum CodingKeys: String, CodingKey {
            case tool = "tool"
            case original = "original"
            case crosshead = "cross-head"
        }
    }

    enum JSONData : Decodable{

        case type(JSONType)
        case item(JSONItem)
        case tool(JSONTool)

        init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
            do{ let temp = try container.decode(JSONType.self); self = .type(temp) ; return}
            catch{do { let temp = try container.decode(JSONItem.self) ; self = .item(temp) ; return}
            catch{ let temp = try container.decode(JSONTool.self)  ; self = .tool(temp) ; return}}
            try  self.init(from: decoder)
        }

        func getValue()-> Any{
            switch self {
            case let .type(x): return x
            case let .item(x): return x
            case let .tool(x): return x
            }
        }
    }


    let result = try JSONDecoder().decode([JSONData].self, from: data1)
    print(result[0].getValue())
    print (result[1].getValue())
    print (result[2].getValue())
0 голосов
/ 22 января 2019

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

Использовать уникальный ключ, которого нет ни в одном другом классе модели

Пример:

var array = NSArray(array: [[
    "type": "apple",
    "kind": "fruit",
    "eatable": true
    ],
    [
        "item": "key",
        "active": true
    ],
    [
    "tool": "screwdriver",
    "original": "toolBox",
    "cross-head": true
    ]])


for model in array as! [NSDictionary]
    {
        if(model.value(forKey: "type") != nil)
        {
            print("use Fruit Model Class")
        }
        else if(model.value(forKey: "item") != nil)
        {
            print("use second model class")
        }
        else
        {
            print("use third model class")
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...