'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