Как я могу использовать Swift для кодирования в словарь?
у меня есть структура, которая реализует Swift 4's Codable
. Есть простой встроенный способ кодирования этой структуры в словарь?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
12 ответов
если вы не против немного сдвига данных вокруг, вы можете использовать что-то вроде этого:
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
}
или дополнительный вариант
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { as? [String: Any] }
}
}
предполагая, что Foo
соответствует Codable
и действительно Encodable
затем вы можете сделать это.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
если вы хотите пойти в другую сторону (init(any)
), взгляните на это Init объект, соответствующий Кодируемому со словарем / массивом
Я создал библиотеку под названием CodableFirebase и его первоначальной целью было использовать его с базой данных Firebase, но на самом деле он делает то, что вам нужно: он создает словарь или любой другой тип, как в JSONDecoder
но вам не нужно делать двойное преобразование здесь, как в других ответах. Так это будет выглядеть примерно так:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Я не уверен, что это лучший способ, но вы определенно можете сделать что-то вроде:
struct Foo: Codable {
var a: Int
var b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
}
let foo = Foo(a: 1, b: 2)
let dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo))
print(dict)
Я определенно думаю, что есть некоторая ценность в том, чтобы просто использовать Codable
для кодирования в / из словарей, без намерения когда-либо ударить JSON/Plists/whatever. Есть много API, которые просто возвращают вам словарь или ожидают словарь, и приятно иметь возможность легко обмениваться ими с Swift-структурами или объектами, без необходимости писать бесконечный шаблонный код.
Я играл с некоторым кодом, основанным на фундаменте JSONEncoder.быстрый источник (который фактически реализует кодирование/декодирование словаря внутри, но не экспортирует его).
код можно найти здесь:https://github.com/elegantchaos/DictionaryCoding
Это все еще довольно грубо, но я немного расширил его, чтобы, например, он мог заполнять отсутствующие значения по умолчанию при декодировании.
Я изменил PropertyListEncoder из проекта Swift в DictionaryEncoder, просто удалив окончательную сериализацию из словаря в двоичный формат. Вы можете сделать то же самое самостоятельно, или вы можете взять мой код здесь
его можно использовать следующим образом:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
в каком-то проекте я использую swift reflection. Но будьте осторожны, вложенные кодируемые объекты также не отображаются.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ (.label!, .value) })
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
нет встроенного способа сделать это.
As Ответил выше если у вас нет проблем с производительностью, то вы можете принять JSONEncoder
+ JSONSerialization
реализация.
но я бы предпочел пойти по пути стандартной библиотеки, чтобы предоставить объект encoder/decoder.
class DictionaryEncoder {
private let jsonEncoder = JSONEncoder()
/// Encodes given Encodable value into an array or dictionary
func encode<T>(_ value: T) throws -> Any where T: Encodable {
let jsonData = try jsonEncoder.encode(value)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
class DictionaryDecoder {
private let jsonDecoder = JSONDecoder()
/// Decodes given Decodable type from given array or dictionary
func decode<T>(_ type: T.Type, from json: Any) throws -> T where T: Decodable {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
return try jsonDecoder.decode(type, from: jsonData)
}
}
вы можете попробовать его со следующим кодом:
struct Computer: Codable {
var owner: String?
var cpuCores: Int
var ram: Double
}
let computer = Computer(owner: "5keeve", cpuCores: 8, ram: 4)
let dictionary = try! DictionaryEncoder().encode(computer)
let decodedComputer = try! DictionaryDecoder().decode(Computer.self, from: dictionary)
Я пытаюсь здесь сделать пример короче. В производственном коде вы должны обрабатывать ошибки соответствующим образом.
Я написал быстрый суть для обработки этого (не используя кодируемый протокол). Будьте осторожны, он не проверяет значения и не работает рекурсивно на значениях, которые можно кодировать.
class DictionaryEncoder {
var result: [String: Any]
init() {
result = [:]
}
func encode(_ encodable: DictionaryEncodable) -> [String: Any] {
encodable.encode(self)
return result
}
func encode<T, K>(_ value: T, key: K) where K: RawRepresentable, K.RawValue == String {
result[key.rawValue] = value
}
}
protocol DictionaryEncodable {
func encode(_ encoder: DictionaryEncoder)
}
нет прямого способа сделать это в Codable. Необходимо реализовать Encodable/протокол Декодируемые для вашей структуры. Для пример, вы могли написать как ниже
typealias EventDict = [String:Int]
struct Favorite {
var all:EventDict
init(all: EventDict = [:]) {
self.all = all
}
}
extension Favorite: Encodable {
struct FavoriteKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: FavoriteKey.self)
for eventId in all {
let nameKey = FavoriteKey(stringValue: eventId.key)!
try container.encode(eventId.value, forKey: nameKey)
}
}
}
extension Favorite: Decodable {
public init(from decoder: Decoder) throws {
var events = EventDict()
let container = try decoder.container(keyedBy: FavoriteKey.self)
for key in container.allKeys {
let fav = try container.decode(Int.self, forKey: key)
events[key.stringValue] = fav
}
self.init(all: events)
}
}
вот простые реализации DictionaryEncoder
/ DictionaryDecoder
что обернуть JSONEncoder
, JSONDecoder
и JSONSerialization
, которые также обрабатывают стратегии кодирования / декодирования...
class DictionaryEncoder {
private let encoder = JSONEncoder()
var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy {
set { encoder.dateEncodingStrategy = newValue }
get { return encoder.dateEncodingStrategy }
}
var dataEncodingStrategy: JSONEncoder.DataEncodingStrategy {
set { encoder.dataEncodingStrategy = newValue }
get { return encoder.dataEncodingStrategy }
}
var nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy {
set { encoder.nonConformingFloatEncodingStrategy = newValue }
get { return encoder.nonConformingFloatEncodingStrategy }
}
var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy {
set { encoder.keyEncodingStrategy = newValue }
get { return encoder.keyEncodingStrategy }
}
func encode<T>(_ value: T) throws -> [String: Any] where T : Encodable {
let data = try encoder.encode(value)
return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
}
}
class DictionaryDecoder {
private let decoder = JSONDecoder()
var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy {
set { decoder.dateDecodingStrategy = newValue }
get { return decoder.dateDecodingStrategy }
}
var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy {
set { decoder.dataDecodingStrategy = newValue }
get { return decoder.dataDecodingStrategy }
}
var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy {
set { decoder.nonConformingFloatDecodingStrategy = newValue }
get { return decoder.nonConformingFloatDecodingStrategy }
}
var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy {
set { decoder.keyDecodingStrategy = newValue }
get { return decoder.keyDecodingStrategy }
}
func decode<T>(_ type: T.Type, from dictionary: [String: Any]) throws -> T where T : Decodable {
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try decoder.decode(type, from: data)
}
}
использование похоже на JSONEncoder
/ JSONDecoder
...
let dictionary = try DictionaryEncoder().encode(object)
и
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
для удобства я поместил все это в репо... https://github.com/ashleymills/SwiftDictionaryCoding
подумайте об этом, вопрос не имеет ответа в общем случае, так как Encodable
экземпляр может быть чем-то не сериализуемым в словаре, например массивом:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
кроме этого, я написал что-то похожее на фреймворк.