Ich habe eine Struktur, die Swift 4 implementiert Codable
. Gibt es eine einfache integrierte Möglichkeit, diese Struktur in ein Wörterbuch zu kodieren?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
Ich habe eine Struktur, die Swift 4 implementiert Codable
. Gibt es eine einfache integrierte Möglichkeit, diese Struktur in ein Wörterbuch zu kodieren?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
Antworten:
Wenn es Ihnen nichts ausmacht, Daten ein wenig zu verschieben, können Sie Folgendes verwenden:
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
}
}
Oder eine optionale Variante
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 { $0 as? [String: Any] }
}
}
Vorausgesetzt, es Foo
entspricht Codable
oder wirklich, Encodable
dann können Sie dies tun.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
Wenn Sie in die andere Richtung gehen möchten ( init(any)
), sehen Sie sich dieses Init an, ein Objekt, das Codable mit einem Wörterbuch / Array entspricht
Hier sind einfache Implementierungen DictionaryEncoder
/ DictionaryDecoder
dass wickeln JSONEncoder
, JSONDecoder
und JSONSerialization
, dass Griff auch Codierung / Decodierung Strategien ...
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)
}
}
Die Verwendung ist ähnlich wie JSONEncoder
/ JSONDecoder
…
let dictionary = try DictionaryEncoder().encode(object)
und
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
Der Einfachheit halber habe ich dies alles in ein Repo eingefügt ... https://github.com/ashleymills/SwiftDictionaryCoding
Ich habe eine Bibliothek namens CodableFirebase erstellt , deren ursprünglicher Zweck darin bestand, sie mit der Firebase-Datenbank zu verwenden, aber sie macht tatsächlich das, was Sie benötigen: Sie erstellt ein Wörterbuch oder einen anderen Typ wie in, JSONDecoder
aber Sie müssen hier keine doppelte Konvertierung durchführen wie du es in anderen Antworten tust. Es würde also ungefähr so aussehen:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Ich bin mir nicht sicher, ob es der beste Weg ist, aber Sie können definitiv etwas tun wie:
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)
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
Dafür gibt es keinen eingebauten Weg. Wie oben beantwortet , können Sie die JSONEncoder
+ JSONSerialization
-Implementierung akzeptieren, wenn Sie keine Leistungsprobleme haben .
Aber ich würde lieber den Weg der Standardbibliothek gehen, um ein Encoder / Decoder-Objekt bereitzustellen.
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)
}
}
Sie können es mit folgendem Code versuchen:
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)
Ich versuche hier, das Beispiel zu verkürzen. Im Produktionscode sollten Sie die Fehler angemessen behandeln.
In einem Projekt habe ich die schnelle Reflexion verwendet. Aber seien Sie vorsichtig, verschachtelte codierbare Objekte werden auch dort nicht zugeordnet.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
Ich denke definitiv, dass es einen gewissen Wert hat, nur in der Lage zu sein Codable
, in / aus Wörterbüchern zu codieren, ohne die Absicht, jemals JSON / Plists / was auch immer zu treffen. Es gibt viele APIs, die Ihnen nur ein Wörterbuch zurückgeben oder ein Wörterbuch erwarten, und es ist schön, sie einfach mit Swift-Strukturen oder -Objekten austauschen zu können, ohne endlosen Boilerplate-Code schreiben zu müssen.
Ich habe mit Code herumgespielt, der auf der Foundation JSONEncoder.swift-Quelle basiert (die die Wörterbuchcodierung / -decodierung zwar intern implementiert, aber nicht exportiert).
Den Code finden Sie hier: https://github.com/elegantchaos/DictionaryCoding
Es ist immer noch ziemlich rau, aber ich habe es ein wenig erweitert, damit beispielsweise fehlende Werte beim Dekodieren mit Standardwerten gefüllt werden können.
Ich habe den PropertyListEncoder aus dem Swift-Projekt in einen DictionaryEncoder geändert , indem ich einfach die endgültige Serialisierung aus dem Wörterbuch in das Binärformat entfernt habe. Sie können das Gleiche selbst tun oder meinen Code von hier aus übernehmen
Es kann folgendermaßen verwendet werden:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
Ich habe einen kurzen Überblick darüber gegeben (ohne das Codable-Protokoll). Seien Sie vorsichtig, es überprüft keine Werte und funktioniert nicht rekursiv für codierbare Werte.
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)
}
In Codable gibt es keine direkte Möglichkeit, dies zu tun. Sie müssen das Encodable / Decodable-Protokoll für Ihre Struktur implementieren. Für Ihr Beispiel müssen Sie möglicherweise wie folgt schreiben
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)
}
}
Ich habe hier einen Pod erstellt https://github.com/levantAJ/AnyCodable , um das Dekodieren und Kodieren zu erleichtern [String: Any]
und[Any]
pod 'DynamicCodable', '1.0'
Und Sie können dekodieren und kodieren [String: Any]
und[Any]
import DynamicCodable
struct YourObject: Codable {
var dict: [String: Any]
var array: [Any]
var optionalDict: [String: Any]?
var optionalArray: [Any]?
enum CodingKeys: String, CodingKey {
case dict
case array
case optionalDict
case optionalArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dict = try values.decode([String: Any].self, forKey: .dict)
array = try values.decode([Any].self, forKey: .array)
optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dict, forKey: .dict)
try container.encode(array, forKey: .array)
try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
}
}
Wenn Sie SwiftyJSON verwenden , können Sie Folgendes tun:
JSON(data: JSONEncoder().encode(foo)).dictionaryObject
Hinweis: Sie können dieses Wörterbuch auch
parameters
für Alamofire- Anforderungen übergeben.
Hier ist eine protokollbasierte Lösung:
protocol DictionaryEncodable {
func encode() throws -> Any
}
extension DictionaryEncodable where Self: Encodable {
func encode() throws -> Any {
let jsonData = try JSONEncoder().encode(self)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
protocol DictionaryDecodable {
static func decode(_ dictionary: Any) throws -> Self
}
extension DictionaryDecodable where Self: Decodable {
static func decode(_ dictionary: Any) throws -> Self {
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try JSONDecoder().decode(Self.self, from: jsonData)
}
}
typealias DictionaryCodable = DictionaryEncodable & DictionaryDecodable
Und so geht's:
class AClass: Codable, DictionaryCodable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
struct AStruct: Codable, DictionaryEncodable, DictionaryDecodable {
var name: String
var age: Int
}
let aClass = AClass(name: "Max", age: 24)
if let dict = try? aClass.encode(), let theClass = try? AClass.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theClass.name), age: \(theClass.age)\"")
}
let aStruct = AStruct(name: "George", age: 30)
if let dict = try? aStruct.encode(), let theStruct = try? AStruct.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theStruct.name), age: \(theStruct.age)\"")
}
Hier ist Wörterbuch -> Objekt. Swift 5.
extension Dictionary where Key == String, Value: Any {
func object<T: Decodable>() -> T? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
return try? JSONDecoder().decode(T.self, from: data)
} else {
return nil
}
}
}
Wenn Sie sich das vorstellen, hat die Frage im allgemeinen Fall keine Antwort, da die Encodable
Instanz möglicherweise nicht in ein Wörterbuch serialisierbar ist, z. B. ein Array:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Davon abgesehen habe ich etwas Ähnliches als Framework geschrieben .