web-dev-qa-db-fra.com

Comment utiliser Swift’s Codable pour encoder dans un dictionnaire?

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]
67
zoul

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

132
Chris Mitchelmore

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)
12
Noobass

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

6
Ashley Mills

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)
6
Lawliet

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) })
2
Nikolay Kapustin

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
}
2
Marmoy

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.

2
5keeve

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.

2
Sam Deane

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)
    }
}
0
Tai Le

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)
}
0
Sid Mani

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.

0
Jakub Truhlář

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)
    }
}
0
Kraming