Risposte:
Se non ti dispiace spostare un po 'di dati intorno a te, potresti usare qualcosa del genere:
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
}
}
O una variante opzionale
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] }
}
}
Supponendo che sia Foo
conforme Codable
o davvero Encodable
, puoi farlo.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
Se vuoi andare dall'altra parte ( init(any)
), dai un'occhiata a questo Iniziare un oggetto conforme a Codable con un dizionario / array
Qui ci sono semplici implementazioni di DictionaryEncoder
/ DictionaryDecoder
che avvolgono JSONEncoder
, JSONDecoder
e JSONSerialization
, che anche gestire la codifica / decodifica di strategie ...
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)
}
}
L'utilizzo è simile a JSONEncoder
/ JSONDecoder
...
let dictionary = try DictionaryEncoder().encode(object)
e
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
Per comodità, ho messo tutto questo in un repo ... https://github.com/ashleymills/SwiftDictionaryCoding
Ho creato una libreria chiamata CodableFirebase e il suo scopo iniziale era di usarla con Firebase Database, ma in realtà fa ciò di cui hai bisogno: crea un dizionario o qualsiasi altro tipo proprio come in JSONDecoder
ma non è necessario fare la doppia conversione qui come fai in altre risposte. Quindi sarebbe simile a:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Non sono sicuro che sia il modo migliore, ma puoi sicuramente fare qualcosa come:
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]
Non esiste un modo integrato per farlo. Come risposto sopra, se non hai problemi di prestazioni, puoi accettare l' implementazione JSONEncoder
+ JSONSerialization
.
Ma preferirei seguire la strada della libreria standard per fornire un oggetto codificatore / decodificatore.
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)
}
}
Puoi provarlo con il seguente codice:
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)
Sto provando con la forza qui per abbreviare l'esempio. Nel codice di produzione dovresti gestire gli errori in modo appropriato.
In alcuni progetti, ho utilizzato la riflessione rapida. Ma attenzione, gli oggetti codificabili annidati non sono mappati anche lì.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
Penso decisamente che ci sia un certo valore nel solo essere in grado di utilizzare Codable
per codificare da / a dizionari, senza l'intenzione di premere mai JSON / Plists / qualunque cosa. Ci sono molte API che ti restituiscono semplicemente un dizionario, o si aspettano un dizionario, ed è bello poterle scambiare facilmente con le strutture o gli oggetti Swift, senza dover scrivere codice boilerplate infinito.
Ho giocato con del codice basato sul sorgente Foundation JSONEncoder.swift (che in realtà implementa internamente la codifica / decodifica del dizionario, ma non lo esporta).
Il codice può essere trovato qui: https://github.com/elegantchaos/DictionaryCoding
È ancora piuttosto approssimativo, ma l'ho espanso un po 'in modo che, ad esempio, possa riempire i valori mancanti con i valori predefiniti durante la decodifica.
Ho modificato il PropertyListEncoder dal progetto Swift in un DictionaryEncoder, semplicemente rimuovendo la serializzazione finale dal dizionario in formato binario. Puoi fare lo stesso da solo o puoi prendere il mio codice da qui
Può essere usato in questo modo:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
Ho scritto un breve riassunto per gestire questo (non utilizzando il protocollo Codable). Fai attenzione, non controlla il tipo di alcun valore e non funziona in modo ricorsivo sui valori che sono codificabili.
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)
}
Non esiste un modo semplice per farlo in Codable. Devi implementare il protocollo Encodable / Decodable per la tua struttura. Per il tuo esempio, potresti dover scrivere come di seguito
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)
}
}
Ho creato un pod qui https://github.com/levantAJ/AnyCodable per facilitare la decodifica e codifica [String: Any]
e[Any]
pod 'DynamicCodable', '1.0'
E sei in grado di decodificare e codificare [String: Any]
e[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)
}
}
Se stai usando SwiftyJSON , potresti fare qualcosa del genere:
JSON(data: JSONEncoder().encode(foo)).dictionaryObject
Nota: puoi anche passare questo dizionario
parameters
per le richieste di Alamofire .
Ecco una soluzione basata su protocollo:
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
Ed ecco come usarlo:
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)\"")
}
Ecco il dizionario -> oggetto. 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
}
}
}
A pensarci bene, la domanda non ha una risposta nel caso generale, poiché l' Encodable
istanza potrebbe essere qualcosa di non serializzabile in un dizionario, come un array:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Oltre a questo, ho scritto qualcosa di simile come framework .