145

我有一个实现 Swift 4 的结构Codable。是否有一种简单的内置方法将该结构编码到字典中?

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

16 回答 16

320

如果您不介意数据的一些转移,您可以使用以下内容:

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

假设Foo符合Codable或真的Encodable那么你可以做到这一点。

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

如果你想另辟蹊径(init(any)),看看这个Init 一个符合 Codable 的对象,带有一个字典/数组

于 2017-09-20T18:12:09.450 回答
29

以下是DictionaryEncoder/的简单实现,DictionaryDecoder它可以包装JSONEncoder,JSONDecoderJSONSerialization,还可以处理编码/解码策略……</p>

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…</p>

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

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

为方便起见,我将所有内容都放在了一个仓库中…… <a href="https://github.com/ashleymills/SwiftDictionaryCoding" rel="noreferrer">https://github.com/ashleymills/SwiftDictionaryCoding

于 2018-09-13T07:16:59.887 回答
18

我创建了一个名为CodableFirebase的库,它的最初目的是将它与 Firebase 数据库一起使用,但它实际上可以满足您的需要:它创建一个字典或任何其他类型,就像 inJSONDecoder但您不需要在这里进行双重转换就像您在其他答案中所做的那样。所以它看起来像:

import CodableFirebase

let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
于 2017-12-29T09:16:25.870 回答
10

没有内置的方法可以做到这一点。如上所述如果您没有性能问题,那么您可以接受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)

我在这里强制尝试使示例更短。在生产代码中,您应该适当地处理错误。

于 2018-09-05T10:03:07.553 回答
7

我不确定这是否是最好的方法,但你绝对可以这样做:

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)
于 2017-07-20T09:02:08.333 回答
6

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

于 2017-11-30T18:30:45.887 回答
4

在某些项目中,我使用了快速反射。但要小心,嵌套的可编码对象也没有映射到那里。

let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
于 2018-09-02T12:31:12.980 回答
3

我绝对认为仅仅能够用于Codable编码到/从字典中是有一些价值的,而不打算去打 JSON/Plists/whatever。有很多 API 只是给你一个字典,或者期望一个字典,很高兴能够轻松地将它们与 Swift 结构或对象互换,而不必编写无休止的样板代码。

我一直在玩一些基于 Foundation JSONEncoder.swift 源的代码(它实际上在内部实现了字典编码/解码,但不导出它)。

代码可以在这里找到:https ://github.com/elegantchaos/DictionaryCoding

它仍然很粗糙,但我对其进行了一些扩展,例如,它可以在解码时使用默认值填充缺失值。

于 2018-02-21T00:02:31.743 回答
2

我已将 Swift 项目中的PropertyListEncoder修改为 DictionaryEncoder,只需将最终序列化从字典中删除为二进制格式即可。你可以自己做同样的事情,或者你可以从这里获取我的代码

它可以这样使用:

do {
    let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
    // handle error
}
于 2018-04-02T09:56:08.367 回答
2

这是一个基于协议的解决方案:

protocol DictionaryEncodable {
    func encode() throws -> Any
}

extension DictionaryEncodable where Self: Encodable {
    func encode() throws -> Any {
        let jsonData = try JSONEncoder().encode(self)
        return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
    }
}

protocol DictionaryDecodable {
    static func decode(_ dictionary: Any) throws -> Self
}

extension DictionaryDecodable where Self: Decodable {
    static func decode(_ dictionary: Any) throws -> Self {
        let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
        return try JSONDecoder().decode(Self.self, from: jsonData)
    }
}

typealias DictionaryCodable = DictionaryEncodable & DictionaryDecodable

以下是如何使用它:

class AClass: Codable, DictionaryCodable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

struct AStruct: Codable, DictionaryEncodable, DictionaryDecodable {
    
    var name: String
    var age: Int
}

let aClass = AClass(name: "Max", age: 24)

if let dict = try? aClass.encode(), let theClass = try? AClass.decode(dict) {
    print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theClass.name), age: \(theClass.age)\"")
}

let aStruct = AStruct(name: "George", age: 30)

if let dict = try? aStruct.encode(), let theStruct = try? AStruct.decode(dict) {
    print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theStruct.name), age: \(theStruct.age)\"")
}
于 2020-07-10T09:00:43.870 回答
1

我写了一个快速的要点来处理这个(不使用 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)
}
于 2017-08-13T05:46:22.770 回答
0

在 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)
    }
}
于 2018-05-26T16:32:24.257 回答
0

我在这里制作了一个 pod https://github.com/levantAJ/AnyCodable以方便解码编码 [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)
    }
}
于 2019-01-19T06:25:01.677 回答
0

这是字典-> 对象。斯威夫特 5。

extension Dictionary where Key == String, Value: Any {

    func object<T: Decodable>() -> T? {
        if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
            return try? JSONDecoder().decode(T.self, from: data)
        } else {
            return nil
        }
    }
}
于 2020-07-20T13:02:09.200 回答
0

经过研究,我们发现如果我们在继承自 Codable & Decodable 的类中使用关键字 Any 会报错。因此,如果您想将字典用户与来自服务器的数据类型一起使用。例如,服务器正在发送 [String : Int] 类型的字典,然后使用 [String : Int] 如果您尝试 [String : Any] 它将不起作用。

于 2020-09-12T00:45:21.930 回答
-6

想想看,这个问题在一般情况下没有答案,因为Encodable实例可能是不能序列化到字典中的东西,例如数组:

let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"

除此之外,我还写了一些类似框架的东西

于 2017-08-17T08:24:37.257 回答