如何在 Swift 中编码编码可编码词典?



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

我尝试Data使用

try! JSONSerialization.data(withJSONObject: dictionary, options: .init(rawValue: 0))

但这崩溃了("JSON 写入中的类型无效 (_SwiftValue("(

我试过使用JSONEncoder

JSONEncoder().encode(dictionary)

但这不会编译("无法推断泛型参数 T"(。

当然,我可以手动将所有可编码转换为 [字符串:任意],然后将其写入用户默认值,但由于 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)

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)

UserDefaults 有一种方法可以保存 [字符串:任何] 字典:

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协议。我知道的最简单的方法是使用JSONSerialization将字典转换为Data对象。以下代码对我有用:

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

要保存和检索UserDefaultsMyObject,您可以执行以下操作:

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

最新更新