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]
46
demandé sur nathan 2017-07-20 11:45:20

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

91
répondu Chris Mitchelmore 2018-07-17 05:50:07

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)
10
répondu Noobass 2017-12-29 09:16:25

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)
6
répondu Lawliet 2017-07-20 09:02:08

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.

2
répondu Sam Deane 2018-02-21 00:02:31

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
}
2
répondu Marmoy 2018-04-04 08:32:20

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) })
2
répondu Nikolay Kapustin 2018-09-02 12:31:12

let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]

1
répondu Ryan Collins 2017-11-30 18:30:45

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.

1
répondu 5keeve 2018-09-05 10:03:07

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)
}
0
répondu Sid Mani 2017-10-30 19:47:05

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)
    }
}
0
répondu Kraming 2018-05-26 16:32:24

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

0
répondu Ashley Mills 2018-09-13 12:37:27

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 .

-4
répondu zoul 2017-10-11 14:17:07