Reputation: 5599
I have a [String: Codable]
dictionary in Swift that I want to save into user defaults, but I'm struggeling to do so.
I've tried converting it to Data
using
try! JSONSerialization.data(withJSONObject: dictionary, options: .init(rawValue: 0))
But this crashes ("Invalid type in JSON write (_SwiftValue)")
I've tried using JSONEncoder
:
JSONEncoder().encode(dictionary)
but this will not compile ("Generic parameter T could not be inferred").
Of course I could manually transform all my Codables into [String: Any] and then write it to user defaults, but since the whole point of Codable is to make Decoding and Encoding easy, I'm not quite sure why the two solutions above are not possible (particularly the second one)?
Example:
For reproducibility you can use this code in a Playground:
import Foundation
struct A: Codable {}
struct B: Codable {}
let dict = [ "a": A(), "b": B() ] as [String : Codable]
let data = try JSONEncoder().encode(dict)
Upvotes: 4
Views: 6842
Reputation: 330
UserDefaults has a way to save [String: Any] dictionaries:
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]
However, if your dictionary is a property of an object that you want to save to UserDefaults
you need to implement the Codable
protocol for your object. The easiest way I know is to convert the dictionary to a Data
object using JSONSerialization
. The following code works for me:
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)
}
}
}
To save and retrieve MyObject
from UserDefaults
you can then do this:
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)
}
}
Upvotes: 2
Reputation: 18581
Codable
, as a generic constraint, and Any
are not encodable. Use a struct instead of a dictionary:
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)
Upvotes: 1