'Top-level Bool encoded as number property list fragment. PropertyListEncoder
I have this generic function to save in NSUserDefaults, in generally works but now I want to save a boolean value and I get an error. I could not find anything and I do not understand why it is not working.
extension UserDefaults {
func saveUserDefaults<T: Codable>(withKey key: String, myType: T) throws{
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch let error {
print("Save Failed")
throw error
}
}
I am calling it like this:
try! UserDefaults().saveUserDefaults(withKey: "String", myType: false)
This is the error I get. I know there is an other way to save boolean values, but I am wondering why it is not working like this?
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.EncodingError.invalidValue(false, Swift.EncodingError.Context(codingPath: [], debugDescription: "Top-level Bool encoded as number property list fragment.", underlyingError: nil))
Thanks!
Solution 1:[1]
A PropertyListEncoder
encodes to a “property list,” and that is always an
array or a dictionary, compare PropertyListSerialization.
Therefore
let data = try PropertyListEncoder().encode(myType)
fails if myType
is a Bool
(or anything which is not an array
or a dictionary).
The possible objects in a property list are also restricted, they can only be instances of
NSData
, NSString
, NSArray
, NSDictionary
, NSDate
, or NSNumber
– or of Swift types
which are bridged to one of those Foundation types.
Solution 2:[2]
As @Martin said a PropertyListEncoder supports only property lists on top level, but not a single fragment of property list like NSNumber. A very simple (though not very elegant) workaround is to wrap any object into array:
let data = try PropertyListEncoder().encode([myType])
UserDefaults.standard.set(data, forKey: key)
And decode it as:
let arr = try PropertyListDecoder().decode([T].self, from: data)
return arr.first
see https://www.marisibrothers.com/2018/07/workaround-for-serializing-codable-fragments.html
Solution 3:[3]
You do not need to encode a Boolean value to save into UserDefaults. You can directly save the boolean into the UserDefaults by calling
let myValue: Bool = false
UserDefaults.standard.set(myValue, forKey: "key")
See Apple docs: UserDefaults.set(_:forKey:).
In fact, Float, Double, Integer, Bool and URL types do not need to be encoded and can directly be saved to UserDefaults.
I see that you have a function that takes a Codable type, encodes it and then saves it to UserDefaults. As Martin R pointed out, you will have to modify that function and check whether passed object can be directly saved to UserDefaults without a need for encoding. It is not necessarily pretty but something like this could work:
switch objectToSave {
case let aFloatType as Float:
UserDefaults.standard.set(aFloatType, forKey: "key")
case let aDoubleType as Double:
UserDefaults.standard.set(aDoubleType, forKey: "key")
case let anIntegerType as Int:
UserDefaults.standard.set(anIntegerType, forKey: "key")
case let aBoolType as Bool:
UserDefaults.standard.set(aBoolType, forKey: "key")
case let aURLType as URL:
UserDefaults.standard.set(aURLType, forKey: "key")
default:
//encode the object as a PropertyList and then save it to UserDefaults
do {
let data = try PropertyListEncoder().encode(objectToSave)
UserDefaults.standard.set(data, forKey: key)
} catch let error {
//the object you passed cannot be encoded as a PropertyList
//possibly because the object cannot be represented as
//NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary - or equivalent Swift types
//also contents of NSArray and NSDictionary have to be one of the types above
print("Save Failed: \(error)")
//perform additional error handling
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Martin R |
Solution 2 | Vladimir |
Solution 3 |