1

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?

Sharad Chauhan
  • 4,821
  • 2
  • 25
  • 50
Matrosov Oleksandr
  • 25,505
  • 44
  • 151
  • 277
  • why you don't store just a json format string and use it for encode and decode? – m1sh0 Jul 30 '19 at 11:49
  • If you are already using Core Data, why don't you use that to store your requests instead of abusing User Defaults? – Paulw11 Jul 30 '19 at 12:02
  • @Paulw11 thanks for comment Paul, but it's super simple to explain. So I have a stack or an array of queued requests which are actually all my operations I wanted to send to the server, but their failed. So in case I use core data objects with isSync flag or any other I still can modify original record of appropriate object while I am offline and when connection is restored I will send modified object in case of any changes with it, I even can delete some record, so then I need to have complex algorithm which search for me dependencies which are not synced yet and update them as well. right? – Matrosov Oleksandr Jul 30 '19 at 14:05
  • I understand what you are trying to do. What I was suggesting is that you store your pending operations in another entity type in Core Data rather than User Defaults. – Paulw11 Jul 30 '19 at 20:38
  • @Paulw11 oh ok got it! thanks! – Matrosov Oleksandr Jul 31 '19 at 08:43

2 Answers2

4

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"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

You are getting error because of [String:Any] I suggest you to convert [String:Any] to Data Using JSONSerialization.data

And Then you can use like this

struct OfflineRequest: Codable {
    let url: String
    let params: Data


    enum CodingKeys: String, CodingKey {
        case url
        case params

    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        params = try values.decode(Data.self, forKey: .params)
        url = try values.decode(String.self, forKey: .url)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(url, forKey: .url)
        try container.encode(params, forKey: .params)
    }
}
Prashant Tukadiya
  • 15,838
  • 4
  • 62
  • 98