Инициировать объект, соответствующий Codable, со словарем / массивом

В первую очередь мой вариант использования - создать объект с помощью словаря: например.

struct Person: Codable { let name: String }    
let dictionary = ["name": "Bob"]
let person = Person(from: dictionary)    

Я хотел бы избежать написания нестандартных реализаций и хочу быть максимально эффективным.


person Chris Mitchelmore    schedule 20.09.2017    source источник


Ответы (3)


На данный момент лучшее решение, которое у меня есть, это это, но оно связано с накладными расходами на кодирование / декодирование.

extension Decodable {
  init(from: Any) throws {
    let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
    let decoder = JSONDecoder()
    self = try decoder.decode(Self.self, from: data)
  }
}

Следуя примеру в вопросе, результат будет

let person = Person(from: dictionary)

Если вы хотите пойти другим путем, это может помочь https://stackoverflow.com/a/46329055/1453346

person Chris Mitchelmore    schedule 20.09.2017
comment
Что за часть DateFormatter для ...? - person Marty; 04.10.2018
comment
@Marty: с помощью Codable вы можете определить свой собственный формат даты в Декодере, чтобы исправить установленные свойства даты объектов. - person smukamuka; 17.10.2018
comment
@smukamuka да, но какое отношение это имеет к вопросу в данном конкретном случае ...? :) - person Marty; 18.10.2018
comment
Ничего такого! Только у моей конкретной проблемы была дата, и тот факт, что сериализация json автоматически кодирует даты и декодируется, меня сначала не смущало, поэтому я оставил это на случай - person Chris Mitchelmore; 18.10.2018
comment
Это отличный ответ. Если вы переходите из stackoverflow.com/a/46329055/1453346, вы должны удалить строки форматирования даты, они нарушают декодирование в этом случае использования - person lewis; 25.03.2020
comment
Я считаю, что .prettyPrinted только добавляет некоторые ненужные накладные расходы, но его влияние должно быть незначительным, потому что использование JSON является основным ударом по производительности. - person Alex Cohn; 21.01.2021

на основе ответа Криса Митчелмора

Подробности

  • Xcode версии 10.3 (10G8), Swift 5

Решение

import Foundation

extension Decodable {

    init(from value: Any,
         options: JSONSerialization.WritingOptions = [],
         decoder: JSONDecoder) throws {
        let data = try JSONSerialization.data(withJSONObject: value, options: options)
        self = try decoder.decode(Self.self, from: data)
    }

    init(from value: Any,
         options: JSONSerialization.WritingOptions = [],
         decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) throws {
        let decoder = JSONDecoder()
        decoderSetupClosure?(decoder)
        try self.init(from: value, options: options, decoder: decoder)
    }

    init?(discardingAnErrorFrom value: Any,
          printError: Bool = false,
          options: JSONSerialization.WritingOptions = [],
          decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) {
        do {
            try self.init(from: value, options: options, decoderSetupClosure: decoderSetupClosure)
        } catch {
            if printError { print("\(Self.self) decoding ERROR:\n\(error)") }
            return nil
        }
    }
}

использование

struct Item: Decodable {
    let id: Int
    let name: String
    let isActive: Bool
    var date: Date
}

let dictionary = ["id": 1, "name": "Item", "is_active": false,
                  "date": "2019-08-06T06:55:00.000-04:00"] as [String : Any]
do {
    let item1 = try Item(from: dictionary) { decoder in
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
        decoder.dateDecodingStrategy = .formatted(dateFormatter)
    }
    print(item1)
} catch {
    print("Error: \(error)")
}

print("\n========================")
let item2 = Item(discardingAnErrorFrom: dictionary)
print(String(describing: item2))

print("\n========================")
let item3 = Item(discardingAnErrorFrom: dictionary, printError: true)
print(String(describing: item3))

print("\n========================")
let item4 = Item(discardingAnErrorFrom: dictionary){ decoder in
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
print(String(describing: item4))

Журнал использования

Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000)

========================
nil

========================
Item decoding ERROR:
keyNotFound(CodingKeys(stringValue: "isActive", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"isActive\", intValue: nil) (\"isActive\").", underlyingError: nil))
nil

========================
Optional(__lldb_expr_5.Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000))
person Vasily Bodnarchuk    schedule 15.08.2019

Я адаптировал ответ Криса Митчелмора так, чтобы он был неудачным инициализатором, а не бросал код. В некоторых случаях делает это немного удобнее.

extension Decodable {
    init?(from: Any) {
        guard let data = try? JSONSerialization.data(withJSONObject: from, options: .prettyPrinted) else { return nil }
        let decoder = JSONDecoder()
        guard let decoded = try? decoder.decode(Self.self, from: data) else { return nil }
        self = decoded
    }
}
person Nico S.    schedule 12.04.2021