As it is closely related to ParseFile
init I would like to rise another question here:
Is there a way how to encode and decode a ParseObject
that has ParseFile
as one of the key into and from JSON?
I am trying to implement “pending saves” on various Realm objects and I got it working well with help od JSON code:
class PendingSave: EmbeddedObject {
/// date when the save operation was triggered
@objc dynamic var date: Date!
/// Original value before any field god modified
@objc dynamic var originalValue: String!
/// Object with pending values to be saved at given `date`
@objc dynamic var pendingSaveValue: String!
convenience init<T:Codable>(pendingValue: T, originalValue: T?) {
self.init()
self.date = Date()
let jsonEncoder = JSONEncoder()
if let originalJsonData = try? jsonEncoder.encode(originalValue) {
let string = String(data: originalJsonData, encoding: String.Encoding.utf8)
self.originalValue = string
}
if let pendingJsonData = try? jsonEncoder.encode(pendingValue) {
let string = String(data: pendingJsonData, encoding: String.Encoding.utf8)
self.pendingSaveValue = string
}
}
func originalObjectValues<T:Codable>(objectType: T.Type) -> T? {
let jsonDecoder = JSONDecoder()
if let data = originalValue.data(using: .utf8) {
return try? jsonDecoder.decode(T.self, from: data)
} else {
return nil
}
}
func pendingObjectValues<T:Codable>(objectType: T.Type) -> T? {
let jsonDecoder = JSONDecoder()
if let data = pendingSaveValue.data(using: .utf8) {
return try? jsonDecoder.decode(T.self, from: data)
} else {
return nil
}
}
/// Has to be called in realm.write{} transaction
func setNewPendingValue<T:Codable>(object: T) {
let jsonEncoder = JSONEncoder()
if let pendingJsonData = try? jsonEncoder.encode(object) {
let string = String(data: pendingJsonData, encoding: String.Encoding.utf8)
self.pendingSaveValue = string
} else {
assertionFailure("failed to set updated pending value")
}
}
static func merge<T:Codable>(object: T, withNewer: T/*, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any*/) -> T {
let encoder = JSONEncoder()
let selfData = try! encoder.encode(object)
let withData = try! encoder.encode(withNewer)
var selfDict = try! JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try! JSONSerialization.jsonObject(with: withData) as! [String: Any]
selfDict.merge(withDict) { (_, new) in new }
let final = try! JSONSerialization.data(withJSONObject: selfDict)
return try! JSONDecoder().decode(T.self, from: final)
}
static func dirtyKeysDictBetween<T:Codable>(object1: T, object2: T) -> [String] {
let encoder = JSONEncoder()
let object1Data = try! encoder.encode(object1)
let object2Data = try! encoder.encode(object2)
let object1Dict = try! JSONSerialization.jsonObject(with: object1Data) as! [String: Any]
let object2Dict = try! JSONSerialization.jsonObject(with: object2Data) as! [String: Any]
let dirtyDict = object1Dict.merging(object2Dict) { (value1, value2) in
//if the values are same, return nil --> reducing dictionary only to dirty keys
return (value1 as! AnyHashable == value2 as! AnyHashable ? nil : value2) as Any
}
return Array(dirtyDict.keys)
}
static func remove<T:Codable>(keys: [String], from object: inout T) {
let encoder = JSONEncoder()
let objectData = try! encoder.encode(object)
var objectDict = try! JSONSerialization.jsonObject(with: objectData) as! [String: Any]
for key in keys {
//setting nil to the dirty keys as the fetched updateAt is newer than pending upload. This way the fetched value wins
objectDict[key] = nil
}
let reducedObjectData = try! JSONSerialization.data(withJSONObject: objectDict)
object = try! JSONDecoder().decode(T.self, from: reducedObjectData)
}
}
But the following decode function returns nil if the ParseObject
struct has ParseFile
there:
func pendingObjectValues<T:Codable>(objectType: T.Type) -> T? {
let jsonDecoder = JSONDecoder()
if let data = pendingSaveValue.data(using: .utf8) {
//This does not decode ParseFile
return try? jsonDecoder.decode(T.self, from: data)
} else {
return nil
}
}
Here is the PendingSave Realm object that it takes data in JSON string (ttl
is encrypted string base64data):
PendingSave {
date = 2021-10-17 10:09:03 +0000;
originalValue = {“createdAt”:656158141.30808997,“ttl”:“ps/i3INnqerjxIWCPiNLsc9oqOMezhxE6vrhmsjcnpM/+0H85j9UDfHO”,“objectId”:“sFGyKan1yH”};
pendingSaveValue = {“createdAt”:656158141.32706594,“ifl”:{“name”:“hi_70cIWkCcEP.jpg”,“__type”:“File”},“objectId”:“sFGyKan1yH”};
}
If I remove “ifl”:{“name”:“hi_70cIWkCcEP.jpg”,"__type":“File”}
from that pendingSaveValue
, the merging, decoding and encoding works as intended