Comment utiliser le Codable de Swift pour encoder dans un dictionnaire?
j'ai une structure qui implémente les Codable
de Swift 4 . Est-t-il un moyen intégré pour coder cette structure dans un dictionnaire?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
12 réponses
si vous ne vous gênez pas un peu de transfert de données autour de vous pourrait 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 facultative
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 { "151910920" as? [String: Any] }
}
}
en supposant que Foo
est conforme à Codable
ou vraiment Encodable
alors 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 à cette Initialisation d'un objet conforme à Codable avec un dictionnaire/tableau
j'ai créé une bibliothèque appelée CodableFirebase et son but initial était de l'utiliser avec la base de données Firebase, mais il fait réellement ce dont vous avez besoin: il crée un dictionnaire ou tout autre type comme dans JSONDecoder
mais vous n'avez pas besoin de faire la conversion double ici comme vous le faites dans d'autres réponses. Donc ça ressemblerait à quelque chose comme:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Je ne suis pas sûr que ce soit 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)
je pense vraiment qu'il y a une certaine valeur à pouvoir utiliser Codable
pour encoder vers/à partir de dictionnaires, sans l'intention de frapper jamais JSON/Plists/n'importe quoi. Il y a beaucoup D'API qui vous donnent juste un dictionnaire, ou attendez-vous à un dictionnaire, et il est agréable d'être en mesure de les échanger facilement avec des structures ou des objets Swift, sans avoir à écrire le code du boilerplate sans fin.
j'ai joué avec un code basé sur le Fondation JSONEncoder.swift source (qui implémente en fait l'encodage/décodage du dictionnaire en interne, mais ne l'exporte pas).
le code se trouve ici: https://github.com/elegantchaos/DictionaryCoding
c'est encore assez rugueux, mais je l'ai élargi un peu pour que, par exemple, il puisse remplir les valeurs manquantes par défaut lors du décodage.
j'ai modifié le PropertyListEncoder du projet Swift en un DictionaryEncoder, simplement en supprimant la sérialisation finale du dictionnaire en format binaire. Vous pouvez faire la même chose vous-même, ou vous pouvez 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
}
dans certains projets, je suis utilisé la réflexion swift. Mais attention, objets codables emboîtés, ne sont pas cartographiés là aussi.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ("151900920".label!, "151900920".value) })
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
Il n'est pas intégré dans la façon de le faire.
Comme répondu ci-dessus si vous n'avez pas de problèmes de performance, alors vous pouvez accepter la JSONEncoder
+ JSONSerialization
mise en œuvre.
mais je préférerais aller à 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)
je suis la force d'essayer ici d'en faire l'exemple le plus court. Dans la production code vous devez traiter les erreurs de manière appropriée.
j'ai écrit un rapide gist pour gérer cela (n'utilisant pas le protocole Codable). Attention, il ne tape pas de valeurs et ne fonctionne pas de façon récursive sur les valeurs qui sont codables.
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)
}
il n'y a pas de façon directe de le faire Codable. Vous devez implémenter un protocole codable/décodable pour votre struct. 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)
}
}
Voici des implémentations simples de DictionaryEncoder
/ DictionaryDecoder
qui enveloppent JSONEncoder
, JSONDecoder
et JSONSerialization
, qui traitent également des stratégies d'encodage / 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 de 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 une... https://github.com/ashleymills/SwiftDictionaryCoding
en y pensant, la question n'a pas de réponse dans le cas général, puisque l'instance Encodable
peut être quelque chose non sérialisable dans un dictionnaire, tel qu'un tableau:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
autre que cela, j'ai écrit quelque chose comme un cadre .