如何使用 Swift 的 Codable 将代码编码到字典中?

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

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

我不知道这是不是最好的方法,但你肯定可以这样做:

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)

我编写了一个快速 大意来处理这个问题(不使用 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)
}

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

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

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

如果你不介意改变一下数据,你可以使用这样的东西:

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)) ,看看这个 使用 dictionary/array 初始化一个符合 Codable 的对象

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

我已经创建了一个名为 CodableFirebase的库,它最初的目的是用于 Firebase 数据库,但它实际上做你需要的: 它创建一个字典或任何其他类型,就像在 JSONDecoder中一样,但你不需要在这里做双转换,就像你在其他答案中做的那样。所以它看起来像这样:

import CodableFirebase


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

我确实认为,使用 Codable编码字典和字典之间的关系是有价值的,而不需要打开 JSON/Plists 或者其他任何东西。有很多 API 只需要返回一个字典,或者期望一个字典,并且能够轻松地与 Swift 结构或对象交换它们,而不必编写没完没了的样板代码,这很好。

我一直在使用一些基于 Foundation JSONEncoder.ift 源代码的代码(它实际上在内部实现了字典编码/解码,但是没有导出它)。

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

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

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

它可以这样使用:

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

在 Codable 没有直截了当的方法。您需要为结构实现可编码/可解码协议。对于您的示例,您可能需要编写如下代码

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

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

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

没有什么方法可以做到这一点。 作为 以上回答,如果您没有性能问题,那么您可以接受 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)

我在这里尝试简化示例。在生产代码中,您应该适当地处理错误。

下面是 DictionaryEncoder/DictionaryDecoder的简单实现,它包装了 JSONEncoderJSONDecoderJSONSerialization,也处理编码/解码策略..。

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

我在这里制作了一个 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)
}
}

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

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

这是 dictionary-> object. Swift 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
}
}
}

经过研究,我们发现,如果我们使用的关键字任何类是从可编码和可解码的继承,它会给出错误。因此,如果希望使用具有来自服务器的数据类型的字典用户。 例如,服务器正在发送类型为[ String: Int ]的字典,然后使用[ String: Int ] ,如果您想尝试[ String: Any] ,那么它将无法工作。