diff --git a/RMJSONPatch.podspec b/RMJSONPatch.podspec index 2544817..79baa9c 100644 --- a/RMJSONPatch.podspec +++ b/RMJSONPatch.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "RMJSONPatch" - s.version = "1.0.4" + s.version = "1.0.4.1" s.summary = "JSONPatch is a swift library for applying and generating RFC-6902 compliant JSON patches." s.module_name = "JSONPatch" @@ -82,7 +82,7 @@ Pod::Spec.new do |s| # Supports git, hg, bzr, svn and HTTP. # - s.source = { :git => "https://github.com/raymccrae/swift-jsonpatch.git", :tag => "v#{s.version}" } + s.source = { :git => "https://github.com/rjga94/swift-jsonpatch.git", :tag => "v#{s.version}" } # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # diff --git a/Sources/JSONPatch/JSONElement.swift b/Sources/JSONPatch/JSONElement.swift index 3f6d414..0b106be 100644 --- a/Sources/JSONPatch/JSONElement.swift +++ b/Sources/JSONPatch/JSONElement.swift @@ -507,6 +507,15 @@ extension JSONElement { } +extension JSONElement { + + func get(fieldName: String) -> JSONElement? { + guard self.isContainer, let dict = self.rawValue as? NSDictionary, let value = dict.value(forKey: fieldName) else { return nil } + + return try? JSONElement(any: value) + } +} + extension JSONElement: Equatable { /// Tests if two JSON Elements are structurally. diff --git a/Sources/JSONPatch/JSONPatchGenerator.swift b/Sources/JSONPatch/JSONPatchGenerator.swift index c2b19b7..349bb33 100644 --- a/Sources/JSONPatch/JSONPatchGenerator.swift +++ b/Sources/JSONPatch/JSONPatchGenerator.swift @@ -21,15 +21,16 @@ import Foundation struct JSONPatchGenerator { - fileprivate enum Operation { case add(path: JSONPointer, value: JSONElement) case remove(path: JSONPointer, value: JSONElement) case replace(path: JSONPointer, old: JSONElement, value: JSONElement) case copy(from: JSONPointer, path: JSONPointer, value: JSONElement) case move(from: JSONPointer, old: JSONElement, path: JSONPointer, value: JSONElement) + case test(path: JSONPointer, value: JSONElement) } + private var identifiers = ["id"] private var unchanged: [JSONPointer: JSONElement] = [:] private var operations: [Operation] = [] private var patchOperations: [JSONPatch.Operation] { @@ -50,14 +51,14 @@ struct JSONPatchGenerator { } switch (a, b) { - case (.object(let dictA), .object(let dictB)), - (.object(let dictA), .mutableObject(let dictB as NSDictionary)), + case let (.object(dictA), .object(dictB)), let + (.object(dictA), .mutableObject(dictB as NSDictionary)), (.mutableObject(let dictA as NSDictionary), .object(let dictB)), (.mutableObject(let dictA as NSDictionary), .mutableObject(let dictB as NSDictionary)): try computeObjectUnchanged(pointer: pointer, a: dictA, b: dictB) - case (.array(let arrayA), .array(let arrayB)), - (.array(let arrayA), .mutableArray(let arrayB as NSArray)), + case let (.array(arrayA), .array(arrayB)), let + (.array(arrayA), .mutableArray(arrayB as NSArray)), (.mutableArray(let arrayA as NSArray), .array(let arrayB)), (.mutableArray(let arrayA as NSArray), .mutableArray(let arrayB as NSArray)): try computeArrayUnchanged(pointer: pointer, a: arrayA, b: arrayB) @@ -88,7 +89,7 @@ struct JSONPatchGenerator { a: NSArray, b: NSArray) throws { let count = min(a.count, b.count) - for index in 0.. target.count { - // target is smaller than source, remove end elements. - for index in (target.count.. String? { + return identifiers.first(where: { node.get(fieldName: $0) != nil }) + } + + private mutating func removeRemaining(_ path: JSONPointer, _ pos: Int, _ srcIdx: inout Int, _ srcSize: Int, _ source: [JSONElement]) { + while srcIdx < srcSize { + let value = source[srcIdx] + let currPath = path.appended(withIndex: pos) + addArrayItemTest(path: currPath, node: value) + operations.append(.remove(path: currPath, value: value)) + srcIdx += 1 + } + } + + private mutating func addRemaining(_ path: JSONPointer, _ target: [JSONElement], _ pos: inout Int, _ targetIdx: inout Int, _ targetSize: Int) -> Int { + while targetIdx < targetSize { + let jsonNode = target[targetIdx] + let currPath = path.appended(withIndex: pos) + try? operations.append(.add(path: currPath, value: jsonNode.copy())) + pos += 1 + targetIdx += 1 + } + return pos + } + private mutating func replace(path: JSONPointer, old: JSONElement, value: JSONElement) { operations.append(.replace(path: path, old: old, value: value)) } @@ -199,14 +270,6 @@ struct JSONPatchGenerator { } private mutating func add(path: JSONPointer, value: JSONElement) { - if let removalIndex = findPreviouslyRemoved(value: value) { - guard case let .remove(removedPath, _) = operations[removalIndex] else { - return - } - operations.remove(at: removalIndex) - operations.append(.move(from: removedPath, old: value, path: path, value: value)) - return - } if let oldPath = findUnchangedValue(value: value) { operations.append(.copy(from: oldPath, path: path, value: value)) } else { @@ -244,12 +307,13 @@ extension JSONPatch.Operation { self = .replace(path: path, value: value) case let .move(from, _, path, _): self = .move(from: from, path: path) + case let .test(path: path, value: value): + self = .test(path: path, value: value) } } } extension JSONPatch { - /// Initializes a JSONPatch instance with all json-patch operations required to transform the source /// json document into the target json document. /// diff --git a/Sources/JSONPatch/NSArray+DeepCopy.swift b/Sources/JSONPatch/NSArrayExtensions.swift similarity index 81% rename from Sources/JSONPatch/NSArray+DeepCopy.swift rename to Sources/JSONPatch/NSArrayExtensions.swift index 4985fa7..7a1ff69 100644 --- a/Sources/JSONPatch/NSArray+DeepCopy.swift +++ b/Sources/JSONPatch/NSArrayExtensions.swift @@ -1,5 +1,5 @@ // -// NSArray+DeepCopy.swift +// NSArrayExtensions.swift // JSONPatch // // Created by Raymond Mccrae on 13/11/2018. @@ -43,4 +43,13 @@ extension NSArray { return result } + func toJSONElementArray() -> [JSONElement] { + var result = [JSONElement]() + self.forEach { element in + if let jsonElement = try? JSONElement(any: element) { + result.append(jsonElement) + } + } + return result + } } diff --git a/Sources/JSONPatch/SwiftLCS.swift b/Sources/JSONPatch/SwiftLCS.swift new file mode 100644 index 0000000..3228c78 --- /dev/null +++ b/Sources/JSONPatch/SwiftLCS.swift @@ -0,0 +1,271 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2015 Tommaso Madonia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +/** +A generic struct that represents a diff between two collections. +*/ +public struct Diff { + + /// The indexes whose corresponding values in the old collection are in the LCS. + public var commonIndexes: [Index] { + return self._commonIndexSet.indexes + } + + /// The indexes whose corresponding values in the new collection are not in the LCS. + public var addedIndexes: [Index] { + return self._addedIndexSet.indexes + } + + /// The indexes whose corresponding values in the old collection are not in the LCS. + public var removedIndexes: [Index] { + return self._removedIndexSet.indexes + } + + /// Construct the `Diff` between two given collections. + public init(_ old: C, _ new: C) where C.Index == Index, C.Element: Equatable { + self = old.diff(new) + } + + fileprivate let _commonIndexSet: DiffIndexSet + fileprivate let _addedIndexSet: DiffIndexSet + fileprivate let _removedIndexSet: DiffIndexSet + + fileprivate init(commonIndexes: DiffIndexSet, addedIndexes: DiffIndexSet, removedIndexes: DiffIndexSet) { + self._commonIndexSet = commonIndexes + self._addedIndexSet = addedIndexes + self._removedIndexSet = removedIndexes + } + +} + +/** + An extension of `Diff`, which adds support for `IndexSet`. + */ +public extension Diff where Index: Strideable, Index.Stride: SignedInteger { + + /// The indexes whose corresponding values in the old collection are in the LCS. + var commonIndexSet: IndexSet { + return self._commonIndexSet.indexSet + } + + /// The indexes whose corresponding values in the new collection are not in the LCS. + var addedIndexSet: IndexSet { + return self._addedIndexSet.indexSet + } + + /// The indexes whose corresponding values in the old collection are not in the LCS. + var removedIndexSet: IndexSet { + return self._removedIndexSet.indexSet + } + +} + +private struct DiffIndexSet { + + let startIndex: Index + let indexes: [Index] + + init(_ indexes: [Index], startIndex: Index) { + self.indexes = indexes + self.startIndex = startIndex + } + +} + +private extension DiffIndexSet where Index: Strideable, Index.Stride: SignedInteger { + + var indexSet: IndexSet { + let indexes = self.indexes.map { Int((self.startIndex..<$0).count) } + + return IndexSet(indexes) + } + +} + +// MARK: - + +/** +An extension of `Collection`, which calculates the diff between two collections. +*/ +public extension Collection where Element: Equatable { + + /** + Returns the diff between two collections. + + - complexity: O(mn) where `m` and `n` are the lengths of the receiver and the given collection. + - parameter collection: The collection with which to compare the receiver. + - returns: The diff between the receiver and the given collection. + */ + func diff(_ otherCollection: Self) -> Diff { + let count = self.count + let commonIndexes = self.longestCommonSubsequence(otherCollection, selfCount: count) + + var removedIndexes: [Index] = [] + removedIndexes.reserveCapacity(count - commonIndexes.count) + + var addedIndexes: [Index] = [] + + var index = self.startIndex + var otherIndex = otherCollection.startIndex + var commonIndexesIterator = commonIndexes.makeIterator() + var commonIndex = commonIndexesIterator.next() + while index != self.endIndex { + if commonIndex == index { + commonIndex = commonIndexesIterator.next() + + while otherIndex != otherCollection.endIndex && otherCollection[otherIndex] != self[index] { + addedIndexes.append(otherIndex) + otherIndex = otherCollection.index(after: otherIndex) + } + + if otherIndex != otherCollection.endIndex { + otherIndex = otherCollection.index(after: otherIndex) + } + } else { + removedIndexes.append(index) + } + + index = self.index(after: index) + } + + while otherIndex != otherCollection.endIndex { + addedIndexes.append(otherIndex) + otherIndex = otherCollection.index(after: otherIndex) + } + + return Diff(commonIndexes: DiffIndexSet(commonIndexes, startIndex: self.startIndex), + addedIndexes: DiffIndexSet(addedIndexes, startIndex: otherCollection.startIndex), + removedIndexes: DiffIndexSet(removedIndexes, startIndex: self.startIndex)) + } + + // MARK: Private functions + + private func prefix(_ otherCollection: Self, count: Int, suffix: Int) -> (Int, [Index]) { + var iterator = self.makeIterator() + var otherIterator = otherCollection.makeIterator() + + var prefix = self.startIndex + let endIndex = self.index(self.startIndex, offsetBy: count - suffix) + while let lhs = iterator.next(), let rhs = otherIterator.next(), lhs == rhs, prefix < endIndex { + prefix = self.index(after: prefix) + } + + let prefixLength = self.distance(from: self.startIndex, to: prefix) + return (prefixLength, Array(self.indices.prefix(prefixLength))) + } + + private func suffix(_ otherCollection: Self, count: Int) -> (Int, [Index]) { + var iterator = self.reversed().makeIterator() + var otherIterator = otherCollection.reversed().makeIterator() + + var offset = count + var suffix = self.index(self.startIndex, offsetBy: offset) + while let lhs = iterator.next(), let rhs = otherIterator.next(), lhs == rhs { + offset &-= 1 + suffix = self.index(self.startIndex, offsetBy: offset) + } + + let suffixLength = self.distance(from: suffix, to: self.endIndex) + return (suffixLength, Array(self.indices.suffix(suffixLength))) + } + + private func computeLCS(_ otherCollection: Self, prefixLength: Int, suffixLength: Int, count: Int) -> [Index] { + let rows = Int(count - prefixLength - suffixLength) + 1 + let columns = Int(otherCollection.count - prefixLength - suffixLength) + 1 + + guard rows > 1 && columns > 1 else { + return [] + } + + var lengths = Array(repeating: 0, count: rows * columns) + var index = self.index(self.startIndex, offsetBy: prefixLength) + for i in 0.. [Index] { + let (suffix, suffixIndexes) = self.suffix(otherCollection, count: count) + let (prefix, prefixIndexes) = self.prefix(otherCollection, count: count, suffix: suffix) + + return prefixIndexes + self.computeLCS(otherCollection, prefixLength: prefix, suffixLength: suffix, count: count) + suffixIndexes + } + +} + +// MARK: - + +/** +An extension of `RangeReplaceableCollection`, which calculates the longest common subsequence between two collections. +*/ +public extension RangeReplaceableCollection where Element: Equatable { + + /** + Returns the longest common subsequence between two collections. + + - parameter collection: The collection with which to compare the receiver. + - returns: The longest common subsequence between the receiver and the given collection. + */ + func longestCommonSubsequence(_ collection: Self) -> Self { + return Self(self.longestCommonSubsequence(collection, selfCount: self.count).map { self[$0] }) + } + +}