From 60f36fcaca3ef9f1545a52972e4006a10981f37f Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 22 Oct 2021 02:01:02 +1100 Subject: [PATCH] strip out unchanged keys on update --- ParseSwift.xcodeproj/project.pbxproj | 10 ++++ Sources/ParseSwift/API/API+Command.swift | 18 ++++--- Sources/ParseSwift/Coding/ParseEncoder.swift | 14 +---- Sources/ParseSwift/Storage/JSONStorage.swift | 56 ++++++++++++++++++++ Sources/ParseSwift/Types/Query.swift | 5 +- 5 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 Sources/ParseSwift/Storage/JSONStorage.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index d4bcbe907..67f703b34 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 4AB8B5051F254AE10070F682 /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4AFDA72A1F26DAE1002AE4FC /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7EE1F254B820063D731 /* Parse.swift */; }; 4AFDA7391F26DAF8002AE4FC /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 65E99B4127219B6900DBBF23 /* JSONStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E99B4027219B6900DBBF23 /* JSONStorage.swift */; }; + 65E99B4227219B6900DBBF23 /* JSONStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E99B4027219B6900DBBF23 /* JSONStorage.swift */; }; + 65E99B4327219B6900DBBF23 /* JSONStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E99B4027219B6900DBBF23 /* JSONStorage.swift */; }; + 65E99B4427219B6900DBBF23 /* JSONStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E99B4027219B6900DBBF23 /* JSONStorage.swift */; }; 7003957625A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; 7003957725A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; 7003957825A0EE770052CB31 /* BatchUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */; }; @@ -782,6 +786,7 @@ 4ACFC2E21F3CA21F0046F3A3 /* ParseSwift.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = ParseSwift.playground; sourceTree = ""; }; 4AFDA7121F26D9A5002AE4FC /* ParseSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4AFDA7151F26D9A5002AE4FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 65E99B4027219B6900DBBF23 /* JSONStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONStorage.swift; sourceTree = ""; }; 7003957525A0EE770052CB31 /* BatchUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchUtilsTests.swift; sourceTree = ""; }; 7003959425A10DFC0052CB31 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; 700395A225A119430052CB31 /* Operations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operations.swift; sourceTree = ""; }; @@ -1562,6 +1567,7 @@ F97B465E24D9C7B500F4A88B /* KeychainStore.swift */, 70572670259033A700F0ADD5 /* ParseFileManager.swift */, F97B45CC24D9C6F200F4A88B /* ParseStorage.swift */, + 65E99B4027219B6900DBBF23 /* JSONStorage.swift */, F97B45CD24D9C6F200F4A88B /* ParseKeyValueStore.swift */, F97B466324D9C88600F4A88B /* SecureStorage.swift */, ); @@ -2040,6 +2046,7 @@ 703B08FD26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA326CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B465224D9C78C00F4A88B /* AddUnique.swift in Sources */, + 65E99B4127219B6900DBBF23 /* JSONStorage.swift in Sources */, 91B79AC826EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 91679D64268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1C26990D7F0051B544 /* ParsePolygon.swift in Sources */, @@ -2249,6 +2256,7 @@ 703B08FE26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA426CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B465324D9C78C00F4A88B /* AddUnique.swift in Sources */, + 65E99B4227219B6900DBBF23 /* JSONStorage.swift in Sources */, 91B79AC926EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 91679D65268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1D26990D7F0051B544 /* ParsePolygon.swift in Sources */, @@ -2553,6 +2561,7 @@ 703B090026BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA626CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B45E524D9C6F200F4A88B /* AnyEncodable.swift in Sources */, + 65E99B4427219B6900DBBF23 /* JSONStorage.swift in Sources */, 91B79ACB26EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 91679D67268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1F26990D7F0051B544 /* ParsePolygon.swift in Sources */, @@ -2676,6 +2685,7 @@ 703B08FF26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA526CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B45E424D9C6F200F4A88B /* AnyEncodable.swift in Sources */, + 65E99B4327219B6900DBBF23 /* JSONStorage.swift in Sources */, 91B79ACA26EE3C5D00073F2C /* API+BatchCommand.swift in Sources */, 91679D66268E596300F71809 /* ParseVersion.swift in Sources */, 91285B1E26990D7F0051B544 /* ParsePolygon.swift in Sources */, diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 8f93e3500..5292c2258 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -358,8 +358,10 @@ internal extension API.Command { // MARK: Saving ParseObjects - private private static func create(_ object: T) -> API.Command where T: ParseObject { - let mapper = { (data) -> T in - try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) + let mapper = { (data: Data) -> T in + let createResult = try ParseCoding.jsonDecoder().decode(SaveResponse.self, from: data).apply(to: object) + JSONStorage.put(object: createResult) + return createResult } return API.Command(method: .POST, path: object.endpoint(.POST), @@ -368,8 +370,10 @@ internal extension API.Command { } private static func update(_ object: T) -> API.Command where T: ParseObject { - let mapper = { (data) -> T in - try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: object) + let mapper = { (data: Data) -> T in + let updateResult = try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: object) + JSONStorage.put(object: updateResult) + return updateResult } return API.Command(method: .PUT, path: object.endpoint, @@ -392,8 +396,10 @@ internal extension API.Command { method: .GET, path: object.endpoint, params: params - ) { (data) -> T in - try ParseCoding.jsonDecoder().decode(T.self, from: data) + ) { (data: Data) -> T in + let fetchResult = try ParseCoding.jsonDecoder().decode(T.self, from: data) + JSONStorage.put(object: fetchResult) + return fetchResult } } } diff --git a/Sources/ParseSwift/Coding/ParseEncoder.swift b/Sources/ParseSwift/Coding/ParseEncoder.swift index af1ec4bcf..b148ffc71 100644 --- a/Sources/ParseSwift/Coding/ParseEncoder.swift +++ b/Sources/ParseSwift/Coding/ParseEncoder.swift @@ -125,12 +125,7 @@ public struct ParseEncoder { internal func encode(_ value: T, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) { - let keysToSkip: Set! - if !ParseSwift.configuration.allowCustomObjectId { - keysToSkip = SkipKeys.object.keys() - } else { - keysToSkip = SkipKeys.customObjectId.keys() - } + let keysToSkip: Set = JSONStorage.getEncodedKeys(object: value) let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: keysToSkip) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = dateEncodingStrategy @@ -147,12 +142,7 @@ public struct ParseEncoder { collectChildren: Bool, objectsSavedBeforeThisOne: [String: PointerType]?, filesSavedBeforeThisOne: [UUID: ParseFile]?) throws -> (encoded: Data, unique: PointerType?, unsavedChildren: [Encodable]) { - let keysToSkip: Set! - if !ParseSwift.configuration.allowCustomObjectId { - keysToSkip = SkipKeys.object.keys() - } else { - keysToSkip = SkipKeys.customObjectId.keys() - } + let keysToSkip: Set = JSONStorage.getEncodedKeys(object: value) let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: keysToSkip) if let dateEncodingStrategy = dateEncodingStrategy { encoder.dateEncodingStrategy = dateEncodingStrategy diff --git a/Sources/ParseSwift/Storage/JSONStorage.swift b/Sources/ParseSwift/Storage/JSONStorage.swift new file mode 100644 index 000000000..349d86f7f --- /dev/null +++ b/Sources/ParseSwift/Storage/JSONStorage.swift @@ -0,0 +1,56 @@ +// +// JSONStorage.swift +// ParseSwift +// +// Created by Daniel Blyth on 21/10/21. +// Copyright © 2021 Parse Community. All rights reserved. +// + +import Foundation +struct JSONStorage { + public static var store: [String: [String: AnyObject]] = [:] + static func put(object: T) where T: ParseObject { + do { + let encoded = try ParseCoding.jsonEncoder().encode(object) + if let objectId = object.objectId, + let json = try JSONSerialization.jsonObject(with: encoded, options: []) as? [String: AnyObject] { + store[objectId] = json + } + } catch { + print("Could not store object") + } + } + static func put(objects: [T]) where T: ParseObject { + for parseObject in objects { + put(object: parseObject) + } + } + static func getEncodedKeys(object: ParseType) -> Set { + var keysToSkip: Set! + if !ParseSwift.configuration.allowCustomObjectId { + keysToSkip = ParseEncoder.SkipKeys.object.keys() + } else { + keysToSkip = ParseEncoder.SkipKeys.customObjectId.keys() + } + guard let objectable = object as? Objectable else { + return keysToSkip + } + do { + let encoded = try ParseCoding.parseEncoder().encode(object) + if let id = objectable.objectId, + let previousStore = store[id], + let json = try JSONSerialization.jsonObject(with: encoded, options: []) as? [String: AnyObject] { + for keyName in json.keys where + (json[keyName] as? NSObject) == (previousStore[keyName] as? NSObject) + && !keysToSkip.contains(keyName) { + keysToSkip.insert(keyName) + } + } + } + } catch { + + } + return keysToSkip +} + +} diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 9f0742262..c1f6f9dbe 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -1641,7 +1641,9 @@ extension Query { func findCommand() -> API.NonParseBodyCommand, [ResultType]> { let query = self return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { - try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + let results = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results + JSONStorage.put(objects: results) + return results } } @@ -1650,6 +1652,7 @@ extension Query { query.limit = 1 return API.NonParseBodyCommand(method: .POST, path: query.endpoint, body: query) { if let decoded = try ParseCoding.jsonDecoder().decode(QueryResponse.self, from: $0).results.first { + JSONStorage.put(object: decoded) return decoded } throw ParseError(code: .objectNotFound,