Respuestas:
Si no le importa un poco de cambio de datos, puede usar algo como esto:
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 opcional
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] }
}
}
Suponiendo que se Foo
ajuste Codable
o realmente Encodable
entonces puede hacer esto.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
Si desea ir al revés ( init(any)
), eche un vistazo a este Init an object conforme a Codable con un diccionario / matriz
Aquí hay implementaciones simples de DictionaryEncoder
/ DictionaryDecoder
que envuelven JSONEncoder
, JSONDecoder
y JSONSerialization
que también manejan estrategias de codificación / decodificación ...
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)
}
}
El uso es similar a JSONEncoder
/ JSONDecoder
…
let dictionary = try DictionaryEncoder().encode(object)
y
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
Para mayor comodidad, he puesto todo esto en un repositorio ... https://github.com/ashleymills/SwiftDictionaryCoding
Creé una biblioteca llamada CodableFirebase y su propósito inicial era usarla con Firebase Database, pero en realidad hace lo que necesita: crea un diccionario o cualquier otro tipo como en, JSONDecoder
pero no necesita hacer la doble conversión aquí. como lo hace en otras respuestas. Entonces se vería algo como:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
No estoy seguro de si es la mejor manera, pero definitivamente puedes hacer algo como:
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]
No hay una forma incorporada de hacer eso. Como se respondió anteriormente, si no tiene problemas de rendimiento, puede aceptar la implementación JSONEncoder
+ JSONSerialization
.
Pero prefiero seguir el camino de la biblioteca estándar para proporcionar un objeto codificador / decodificador.
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)
}
}
Puedes probarlo con el siguiente código:
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)
Estoy intentando forzar aquí acortar el ejemplo. En el código de producción, debe manejar los errores de manera adecuada.
En algún proyecto, utilizo la reflexión rápida. Pero tenga cuidado, los objetos codificables anidados no están mapeados allí también.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
Definitivamente creo que hay algo de valor en poder usar Codable
para codificar hacia / desde diccionarios, sin la intención de llegar a JSON / Plists / lo que sea. Hay muchas API que simplemente le devuelven un diccionario, o esperan un diccionario, y es bueno poder intercambiarlas fácilmente con estructuras u objetos Swift, sin tener que escribir un código repetitivo interminable.
He estado jugando con un código basado en la fuente Foundation JSONEncoder.swift (que en realidad implementa la codificación / decodificación del diccionario internamente, pero no lo exporta).
El código se puede encontrar aquí: https://github.com/elegantchaos/DictionaryCoding
Todavía es bastante tosco, pero lo he ampliado un poco para que, por ejemplo, pueda completar los valores faltantes con valores predeterminados al decodificar.
He modificado PropertyListEncoder del proyecto Swift en un DictionaryEncoder, simplemente eliminando la serialización final del diccionario en formato binario. Puede hacer lo mismo usted mismo, o puede tomar mi código desde aquí
Se puede usar así:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
Escribí una breve descripción para manejar esto (sin usar el protocolo codificable). Tenga cuidado, no verifica ningún valor y no funciona de forma recursiva en valores que son codificables.
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)
}
No hay una forma sencilla de hacer esto en Codable. Debe implementar el protocolo codificable / decodificable para su estructura. Para su ejemplo, es posible que deba escribir lo siguiente
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)
}
}
He creado un pod aquí https://github.com/levantAJ/AnyCodable para facilitar la decodificación y codificación [String: Any]
y[Any]
pod 'DynamicCodable', '1.0'
Y puedes decodificar y codificar [String: Any]
y[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)
}
}
Si está utilizando SwiftyJSON , podría hacer algo como esto:
JSON(data: JSONEncoder().encode(foo)).dictionaryObject
Nota: también puede pasar este diccionario
parameters
a las solicitudes de Alamofire .
Aquí hay una solución basada en protocolo:
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
Y así es como se usa:
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)\"")
}
Aquí está el diccionario -> objeto. Rápido 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
}
}
}
Ahora que lo pienso, la pregunta no tiene respuesta en el caso general, ya que la Encodable
instancia puede ser algo no serializable en un diccionario, como una matriz:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Aparte de eso, he escrito algo similar como marco .