Search code examples
iosswiftnsuserdefaultscodabledecodable

Swift - Save array of [String: Any] to NSUserDefauls


I have found this extension to use Codable to be saved into NSUserDefaults

extension UserDefaults {
    func decode<T : Codable>(for type : T.Type, using key : String) -> T? {
        let defaults = UserDefaults.standard
        guard let data = defaults.object(forKey: key) as? Data else {return nil}
        let decodedObject = try? PropertyListDecoder().decode(type, from: data)
        return decodedObject
    }

    func encode<T : Codable>(for type : T, using key : String) {
        let defaults = UserDefaults.standard
        let encodedData = try? PropertyListEncoder().encode(type)
        defaults.set(encodedData, forKey: key)
        defaults.synchronize()
    }
}

But as I see I have an error Type 'OfflineRequest' does not conform to protocol 'Decodable' looks like because of Any.

I have next structure I want to save:

struct OfflineRequest: Codable {
    let url: String
    let params: [String: Any]
}

The idea is to persistence store stack (array) of requests which are unsuccessful because of any connection issues. So I have Core Data data model and I am converting its properties to [String: Any] before sending it to the server. But now I want to create offline first algorithm. So in case user is offline I want to persistent store url and params which is [String: Any]. How to handle does not conform to protocol 'Decodable'correctly in this case?


Solution

  • You can use JSONSerialization.data(withJSONObject: Any) to encode your dictionary and JSONSerialization.JSONObject(with: Data) to decode it. You just need to make sure you pass a valid json object (in this case a dictionary), if you pass a dictionary with types like a Date it will crash. Try like this:

    struct OfflineRequest: Codable {
        let url: String
        let params: [String: Any]
    }
    

    extension OfflineRequest {
        init(from decoder: Decoder) throws {
            var container = try decoder.unkeyedContainer()
            url = try container.decode(String.self)
            params = try JSONSerialization.jsonObject(with: container.decode(Data.self)) as? [String:Any] ?? [:]
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.unkeyedContainer()
            try container.encode(url)
            try container.encode(JSONSerialization.data(withJSONObject: params))
        }
    }
    

    let test = [OfflineRequest(url: "http://www.google.com", params: ["a":"1","b":"test", "c":["d":"test"]])]
    
    let data = try! JSONEncoder().encode(test)
    let loaded = try! JSONDecoder().decode([OfflineRequest].self, from: data)
    print(loaded) // [__lldb_expr_3.OfflineRequest(url: "http://www.google.com", params: ["b": test, "a": 1, "c": {\n    d = test;\n}])]\n"