Расшифруйте все свойства в пользовательском init (перечислите все свойства класса и присвойте им значение) - PullRequest
0 голосов
/ 15 октября 2018

В настоящее время я работаю над проектом, API которого еще не готов.Так что иногда меняется тип некоторых свойств.Например, у меня есть такая структура:

struct Animal: Codable {
    var tag: Int?
    var name: String?
    var type: String?
    var birthday: Date?
}

для этого json:

{
    "tag": 12,
    "name": "Dog",
    "type": "TYPE1"
}

Но в процессе разработки json изменяется примерно так:

{
    "tag": "ANIMAL",
    "name": "Dog",
    "type": 1
}

Таким образом, я получаю некоторую несоответствие типов ошибку в декодере и nil object .Чтобы не допустить сбоя декодера всего объекта, я реализую пользовательский init и устанавливаю nil для любого неизвестного свойства, и он работает как charm ( ПРИМЕЧАНИЕ. Я обработаю эти изменения позжеи это только для незапланированных и временных изменений ):

#if DEBUG
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    tag = (try? container.decodeIfPresent(Int.self, forKey: .tag)) ?? nil
    name = (try? container.decodeIfPresent(String.self, forKey: .name)) ?? nil
    type = (try? container.decodeIfPresent(String.self, forKey: .type)) ?? nil
    birthday = (try? container.decodeIfPresent(Date.self, forKey: .birthday)) ?? nil
}
#endif

Но для больших классов и структур я должен написать любое свойство вручную, и это требует времени и, что более важно, иногда я пропускаю свойствоустановить!

Так есть ли способ перечислить все свойства и установить их?Я знаю, что могу получить все ключи из контейнера, но не знаю, как установить соответствующее свойство:

for key in container.allKeys {
   self.<#corresponding_property#> = (try? container.decodeIfPresent(<#corresponding_type#>.self, forKey: key)) ?? nil
}

Спасибо

Ответы [ 2 ]

0 голосов
/ 15 октября 2018

Инструмент, который вам нужен: Sourcery .Это оболочка метапрограммирования для Swift, которая напишет шаблон для вас, так как вы знаете, что вы хотите написать, это просто утомительно (для этого идеально подходит Sourcery).Важная вещь в Sourcery (несмотря на название), нет волшебства.Он просто пишет код Swift для вас на основе другого кода Swift.Это позволяет легко вытащить позже, когда вам это больше не нужно.

0 голосов
/ 15 октября 2018

Проблема в вашем конкретном примере заключается в том, что тип ваших значений постоянно меняется: иногда tag - это строка, иногда целое число.Вам понадобится намного больше, чем ваш необязательный подход;это касается того, присутствует ли что-то, а не имеет ли оно правильный тип.Вам понадобится тип объединения, который может декодировать и представлять строку или целое число, например:

enum Sint : Decodable {
    case string(String)
    case int(Int)
    enum Err : Error { case oops }
    init(from decoder: Decoder) throws {
        let con = try decoder.singleValueContainer()
        if let s = try? con.decode(String.self) {
            self = .string(s)
            return
        }
        if let i = try? con.decode(Int.self) {
            self = .int(i)
            return
        }
        throw Err.oops
    }
}

Используя это, я смог декодировать оба ваших примера, используя один тип структуры Animal:

struct Animal: Decodable {
    var tag: Sint
    var name: String
    var type: Sint
}
let j1 = """
{
    "tag": 12,
    "name": "Dog",
    "type": "TYPE1"
}
"""
let j2 = """
{
    "tag": "ANIMAL",
    "name": "Dog",
    "type": 1
}
"""
let d1 = j1.data(using: .utf8)!
let a1 = try! JSONDecoder().decode(Animal.self, from: d1)
let d2 = j2.data(using: .utf8)!
let a2 = try! JSONDecoder().decode(Animal.self, from: d2)

Хорошо, но теперь допустим, что вы даже не знаете, какими будут ключи.Затем вам нужен тип AnyCodingKey, который может убирать ключи независимо от того, какие они есть, и вместо нескольких свойств у вашего Animal будет единственное свойство - словарь, например:

struct Animal: Decodable {
    var d = [String : Sint]()
    struct AnyCodingKey : CodingKey {
        var stringValue: String
        var intValue: Int?

        init(_ codingKey: CodingKey) {
            self.stringValue = codingKey.stringValue
            self.intValue = codingKey.intValue
        }
        init(stringValue: String) {
            self.stringValue = stringValue
            self.intValue = nil
        }
        init(intValue: Int) {
            self.stringValue = String(intValue)
            self.intValue = intValue
        }
    }
    init(from decoder: Decoder) throws {
        let con = try decoder.container(keyedBy: AnyCodingKey.self)
        for key in con.allKeys {
            let result = try con.decode(Sint.self, forKey: key)
            self.d[key.stringValue] = result
        }
    }
}

Итак, теперь выможет декодировать что-либо с помощью полностью неизвестных ключей, значение которых может быть строкой или целым числом.Опять же, это прекрасно работает с примерами JSON, которые вы привели.

Обратите внимание, что это обратное тому, что вы изначально просили сделать.Вместо того чтобы использовать имена свойств структуры для генерации ключей, я просто принял любой ключ любого типа и гибко сохранил его в структуре с помощью словаря.Вы могли бы также поместить фасад свойства перед этим словарем, используя новую функцию Swift 4.2 dynamicMemberLookup.Но это оставлено как упражнение для читателя!

...