如何使用 Swift 的 Codable 编码成字典?

Posted

技术标签:

【中文标题】如何使用 Swift 的 Codable 编码成字典?【英文标题】:How can I use Swift’s Codable to encode into a dictionary? 【发布时间】:2017-12-25 20:34:57 【问题描述】:

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

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

【问题讨论】:

***.com/a/46597941/2303865 【参考方案1】:

如果您不介意周围的数据移动一点,可以使用以下方法:

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 an object conforming to Codable with a dictionary/array

【讨论】:

可选的 var 实现非常棒、干净、快速,非常适合用于保护 let 语句。真正清理 API 调用。 编码成数据再从数据中解码,解码大块数据时,性能上的惩罚一定很明显。【参考方案2】:

这里是 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)

为方便起见,我把这些都放在了一个 repo 中……https://github.com/ashleymills/SwiftDictionaryCoding

【讨论】:

非常感谢!替代方法是使用继承,但调用站点无法将类型推断为字典,因为会有 2 个不同返回类型的函数。 比公认的答案好得多。 +1【参考方案3】:

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

import CodableFirebase

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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)

【讨论】:

这只适用于所有属性相同的结构 我刚刚尝试了“让 dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo))”,我得到了“预期解码字典 但找到了一个数组。”你能帮忙吗【参考方案6】:

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

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

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

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

【讨论】:

【参考方案9】:

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

可以这样使用:

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

【讨论】:

【参考方案10】:

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

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

【讨论】:

【参考方案11】:

这里是字典 -> 对象。斯威夫特 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
        
    

【讨论】:

【参考方案12】:

我写了一个快速的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)

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

我在这里做了一个 pod https://github.com/levantAJ/AnyCodable 方便 decodeencode [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)
    

【讨论】:

您的示例没有显示如何解决问题【参考方案15】:

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

【讨论】:

【参考方案16】:

如果你使用 SwiftyJSON,你可以这样做:

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

注意:您也可以将此字典作为 parameters 传递给 Alamofire 请求。

【讨论】:

【参考方案17】:

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

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

除此之外,我写了something similar as a framework。

【讨论】:

我不得不承认我仍然不明白为什么这被否决:-) 警告不是真的吗?还是框架没用?

以上是关于如何使用 Swift 的 Codable 编码成字典?的主要内容,如果未能解决你的问题,请参考以下文章

Swift Codable - 如何编码和解码字符串化的 JSON 值?

如何使用 Swift 创建 JSON Codable [关闭]

如何在 Core Data 中使用 swift 4 Codable?

如何在 Core Data 中使用 swift 4 Codable?

如何在 Core Data 中使用 swift 4 Codable?

如何从 Swift Codable 中排除属性?