J'ai une structure qui implémente la variable Codable
de Swift 4. Existe-t-il un moyen simple et intégré de coder cette structure dans un dictionnaire?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
Si cela ne vous dérange pas de changer les données, vous pouvez utiliser quelque chose comme ceci:
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
}
}
Ou une variante optionnelle
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] }
}
}
En supposant que Foo
soit conforme à Codable
ou vraiment Encodable
, vous pouvez le faire.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
Si vous voulez aller dans l'autre sens (init(any)
), jetez un oeil à ceci Initiez un objet conforme à Codable avec un dictionnaire/array
J'ai créé une bibliothèque appelée CodableFirebase et son objectif initial était de l’utiliser avec Firebase Database, mais c’est ce dont vous avez besoin: il crée un dictionnaire ou tout autre type, tout comme dans JSONDecoder
mais vous n’avez pas besoin de le faire. faites la double conversion ici comme vous le faites dans d'autres réponses. Donc, cela ressemblerait à quelque chose comme:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Voici des implémentations simples de DictionaryEncoder
/DictionaryDecoder
qui encapsulent JSONEncoder
, JSONDecoder
et JSONSerialization
, qui gèrent également des stratégies de codage/décodage…
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'utilisation est similaire à JSONEncoder
/JSONDecoder
…
let dictionary = try DictionaryEncoder().encode(object)
et
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
Pour plus de commodité, j’ai mis tout cela dans un compte-rendu… https://github.com/ashleymills/SwiftDictionaryCoding
Je ne sais pas si c'est la meilleure façon, mais vous pouvez certainement faire quelque chose comme:
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)
Dans certains projets, j'ai utilisé la réflexion de Swift. Mais attention, les objets codables imbriqués ne sont pas mappés également là-bas.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
J'ai modifié le PropertyListEncoder du projet Swift dans un DictionaryEncoder, simplement en supprimant la sérialisation finale du dictionnaire au format binaire. Vous pouvez faire la même chose vous-même ou prendre mon code de ici
Il peut être utilisé comme ceci:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
Il n’existe pas de méthode intégrée pour le faire . Comme la réponse ci-dessus si vous n’avez aucun problème de performances, vous pouvez accepter l’implémentation JSONEncoder
+ JSONSerialization
.
Mais je préférerais utiliser la méthode standard de la bibliothèque standard pour fournir un objet encodeur/décodeur.
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)
}
}
Vous pouvez l'essayer avec le code suivant:
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)
J'essaie de force ici pour rendre l'exemple plus court. Dans le code de production, vous devez gérer les erreurs de manière appropriée.
Je pense vraiment qu’il est utile de pouvoir simplement utiliser Codable
pour encoder dans/à partir de dictionnaires, sans l’intention de frapper JSON/Plists/peu importe. Il existe de nombreuses API qui ne font que restituer un dictionnaire, ou attendent un dictionnaire, et il est agréable de pouvoir les échanger facilement avec des structures ou des objets Swift, sans avoir à écrire du code passe-partout sans fin.
J'ai joué avec du code basé sur le source Foundation JSONEncoder.Swift (qui implémente l'encodage/le décodage de dictionnaire en interne, mais ne l'exporte pas).
Le code peut être trouvé ici: https://github.com/elegantchaos/DictionaryCoding
C'est encore assez dur, mais je l'ai un peu développé pour pouvoir par exemple compléter les valeurs manquantes par défaut lors du décodage.
J'ai créé un module ici https://github.com/levantAJ/AnyCodable pour faciliter décoder et encoder[String: Any]
et [Any]
pod 'DynamicCodable', '1.0'
Et vous êtes capable de décoder et encoder [String: Any]
et [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)
}
}
J'ai écrit un rapide Gist pour gérer cela (sans utiliser le protocole Codable). Attention, il ne vérifie pas les valeurs et ne fonctionne pas de manière récursive sur les valeurs encodables.
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)
}
Si vous utilisez SwiftyJSON , vous pouvez faire quelque chose comme ceci:
JSON(data: JSONEncoder().encode(foo)).dictionaryObject
Remarque: Vous pouvez également transmettre ce dictionnaire sous la forme
parameters
à Alamofire request.
Il n'y a pas de moyen simple de faire cela dans Codable. Vous devez implémenter le protocole Encodable/Decodable pour votre structure. Pour votre exemple, vous pourriez avoir besoin d'écrire comme ci-dessous
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)
}
}