如何在Swift中编码Codables字典?

时间:2018-10-12 15:14:34

标签: json swift dictionary encoding codable

我在Swift中有一个[String: Codable]字典,我想保存为用户默认值,但是我在努力做到这一点。

我尝试使用

将其转换为Data
try! JSONSerialization.data(withJSONObject: dictionary, options: .init(rawValue: 0))

但这会崩溃(“ JSON写入(_SwiftValue)中的无效类型”)

我尝试使用JSONEncoder

JSONEncoder().encode(dictionary)

但这不会编译(“无法推断出通用参数T”)。

当然,我可以手动将所有Codable转换为[String:Any],然后将其写入用户默认值,但是由于Codable的全部目的是简化解码和编码,所以我不确定为什么两者上面的解决方案是不可能的(尤其是第二种)?

示例

为了可重复性,您可以在Playground中使用以下代码:

import Foundation

struct A: Codable {}
struct B: Codable {}

let dict = [ "a": A(), "b": B() ] as [String : Codable]
let data = try JSONEncoder().encode(dict)

2 个答案:

答案 0 :(得分:2)

Codable作为一般约束,而Any是不可编码的。使用结构而不是字典:

struct A: Codable {
    let a = 0
}
struct B: Codable {
    let b = "hi"
}
struct C: Codable {
    let a: A
    let b: B
}

let d = C(a: A(), b: B())
let data = try JSONEncoder().encode(d)

答案 1 :(得分:0)

UserDefaults可以保存[String:Any]字典:

let myDictionary: [String: Any] = ["a": "one", "b": 2]
UserDefaults.standard.set(myDictionary, forKey: "key")
let retrievedDictionary: [String: Any] = UserDefaults.standard.dictionary(forKey: "key")!
print(retrievedDictionary)      // prints ["a": one, "b": 2]

但是,如果字典是要保存到UserDefaults的对象的属性,则需要为该对象实现Codable协议。我知道的最简单的方法是使用Data将字典转换为JSONSerialization对象。以下代码对我有用:

class MyObject: Codable {

    let dictionary: [String: Any]

    init(dictionary: [String: Any]) {
        self.dictionary = dictionary
    }

    enum CodingKeys: String, CodingKey {
        case dictionary
    }

    public required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if values.contains(.dictionary), let jsonData = try? values.decode(Data.self, forKey: .dictionary) {
            dictionary = (try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]) ??  [String: Any]()
        } else {
            dictionary = [String: Any]()
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if !dictionary.isEmpty, let jsonData = try? JSONSerialization.data(withJSONObject: dictionary) {
            try container.encode(jsonData, forKey: .dictionary)
        }
    }
}

要从MyObject中保存和检索UserDefaults,则可以执行以下操作:

extension UserDefaults {

    func set(_ value: MyObject, forKey defaultName: String) {
        guard let data = try? PropertyListEncoder().encode(value) else { return }
        set(data, forKey: defaultName)
    }

    func myObject(forKey defaultName: String) -> MyObject? {
        guard let data = data(forKey: defaultName) else { return nil }
        return try? PropertyListDecoder().decode(MyObject.self, from: data)
    }
}