web-dev-qa-db-ja.com

SwiftのCodableを使用して辞書にエンコードする方法を教えてください。

Swift 4のCodableを実装する構造体があります。その構造体を辞書にエンコードするための単純な組み込みの方法はありますか?

let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
82
zoul

ちょっとしたデータの移動を気にしないのであれば、次のようなものを使用できます。

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

またはオプションのバリアント

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] }
  }
}

FooCodableまたは実際にEncodableに準拠していると仮定すると、これを実行できます。

let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary

他のやり方をしたい場合は(init(any))、これを見てください 辞書/配列でCodableに準拠したオブジェクトを初期化してください

160

CodableFirebase というライブラリを作成しましたが、最初の目的はFirebase Databaseでそれを使用することでしたが、実際には必要なことを実行します。それはJSONDecoderのように辞書またはその他のタイプを作成します。しかし、他の答えのようにここで二重変換をする必要はありません。だからそれは次のようになります。

import CodableFirebase

let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
14
Noobass

これは、DictionaryEncoderDictionaryDecoder、およびJSONEncoderをラップするJSONDecoder/JSONSerializationの単純な実装で、エンコード/デコード戦略も処理します。…

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

使い方はJSONEncoder/JSONDecoder…に似ています

let dictionary = try DictionaryEncoder().encode(object)

そして

let object = try DictionaryDecoder().decode(Object.self, from: dictionary)

便宜上、私はこれをすべてリポジトリに入れました… https://github.com/ashleymills/SwiftDictionaryCoding

12
Ashley Mills

それが最善の方法かどうかはわかりませんが、間違いなく次のようなことができます。

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

それを行うための方法は組み込まれていません。あなたがパフォーマンスの問題を抱えていなければ 上記で答えた そしてあなたはJSONEncoder + JSONSerializationの実装を受け入れることができます。

しかし、私はむしろ標準ライブラリの方法でエンコーダ/デコーダオブジェクトを提供したいと思います。

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

あなたは次のコードでそれを試すことができます:

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)

ここでは例を短くするために力を入れています。本番コードでは、エラーを適切に処理する必要があります。

3
5keeve

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

3
Ryan Collins

あるプロジェクトでは、私はSwiftリフレクションを使いました。しかし、注意してください、ネストされたコード化可能オブジェクトは、そこでもマッピングされていません。

let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
2

私は PropertyListEncoder をSwiftプロジェクトからDictionaryEncoderに変更しました。単純に、辞書からバイナリ形式への最終的なシリアル化を削除します。あなたは自分自身で同じことをすることができます、またはあなたは私のコードをから取ることができます ここ

それはこのように使用することができます:

do {
    let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
    // handle error
}
2
Marmoy

JSON/Plistsなどを叩くことを意図せずに、辞書との間でエンコードするためにCodableを使用できることには、ある程度の価値があると確信しています。辞書を返してくれる、または辞書を期待するだけのAPIがたくさんあります。それらをSwiftの構造体やオブジェクトと簡単に交換できるのは、無限の定型コードを作成する必要がないのはいいことです。

私はFoundation JSONEncoder.Swiftソース(実際には辞書のエンコード/デコードを内部的に実装していますが、エクスポートはしていません)に基づいたコードをいくつか試してきました。

コードはここで見つけることができます: https://github.com/elegantchaos/DictionaryCoding

まだかなりラフですが、デコード時に欠損値をデフォルトで埋められるように、少し拡張しました。

2
Sam Deane

私はここにポッドを作りました https://github.com/levantAJ/AnyCodableデコードを容易にするために==そしてencode[String: Any][Any]

pod 'DynamicCodable', '1.0'

そして[String: Any][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

私はこれを処理するためにquick Gist を書きました(Codableプロトコルを使わないで)。値を型チェックせず、エンコード可能な値に対して再帰的に機能しないように注意してください。

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

もしあなたがSwiftyJSONを使っているなら、あなたはこのようなことをすることができます:

JSON(data: JSONEncoder().encode(foo)).dictionaryObject

注:この辞書をparametersとして渡してAlamofire要求に渡すこともできます。

0
Jakub Truhlář

Codableでこれを行う直接的な方法はありません。あなたはあなたの構造体のためのEncodable/Decodableプロトコルを実装する必要があります。あなたの例では、あなたは以下のように書く必要があるかもしれません

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