├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENCE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── com │ └── tananaev │ └── jsonpatch │ ├── JsonPatch.java │ ├── JsonPatchException.java │ ├── JsonPatchFactory.java │ ├── JsonPath.java │ ├── LongestCommonSubsequenceFactory.java │ ├── PostProcessor.java │ ├── gson │ ├── AbsOperationDeserializer.java │ ├── JsonPathDeserializer.java │ └── JsonPathSerializer.java │ └── operation │ ├── AbsOperation.java │ ├── AddOperation.java │ ├── InPlaceElementWrapper.java │ ├── MoveOperation.java │ ├── RemoveOperation.java │ └── ReplaceOperation.java └── test ├── java └── com │ └── tananaev │ └── jsonpatch │ ├── JsonPatchTest.java │ └── test │ ├── JsonPatchFactoryTest.java │ ├── JsonPathTest.java │ ├── LongestCommonSubsequenceFactoryTest.java │ ├── OperationTest.java │ ├── operation │ ├── AddOperationTest.java │ ├── MoveOperationTest.java │ ├── RemoveOperationTest.java │ └── ReplaceOperationTest.java │ └── util │ ├── JsonPatchTestCase.java │ └── LCSFTestCase.java └── resources ├── small ├── origin.json ├── patch.json └── result.json ├── sublist.json └── test_cases.json /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Gradle 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-java@v1 17 | with: 18 | java-version: 1.8 19 | - run: chmod +x gradlew 20 | - run: ./gradlew build 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | json_patch.iml 4 | target 5 | build/ 6 | .gradle/ 7 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Patch Library - json-patch 2 | 3 | ## Overview 4 | 5 | Java implementation of [RFC 6902 (JSON Patch)](http://tools.ietf.org/html/rfc6902) standard. This library is based on Google Gson JSON library. 6 | 7 | This project is a fork of the [original library](https://github.com/riotopsys/json-patch) developed by C. A. Fitzgerald. 8 | 9 | ## Usage 10 | 11 | Include dependency via Gradle: 12 | ```groovy 13 | compile 'com.tananaev:json-patch:1.2' 14 | ``` 15 | or Maven: 16 | ```xml 17 | 18 | com.tananaev 19 | json-patch 20 | 1.1 21 | 22 | ``` 23 | 24 | ### Patch creation 25 | ``` 26 | JsonPatchFactory jpf = new JsonPatchFactory(); 27 | JsonPatch patch = jpf.create(original, target); 28 | ``` 29 | 30 | ### Patch application 31 | ``` 32 | JsonElement target = patch.apply(original); 33 | ``` 34 | 35 | ## License 36 | 37 | Apache License, Version 2.0 38 | 39 | Licensed under the Apache License, Version 2.0 (the "License"); 40 | you may not use this file except in compliance with the License. 41 | You may obtain a copy of the License at 42 | 43 | http://www.apache.org/licenses/LICENSE-2.0 44 | 45 | Unless required by applicable law or agreed to in writing, software 46 | distributed under the License is distributed on an "AS IS" BASIS, 47 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 48 | See the License for the specific language governing permissions and 49 | limitations under the License. 50 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: 'signing' 4 | 5 | group = 'com.tananaev' 6 | version = '1.2' 7 | 8 | sourceCompatibility = 1.7 9 | 10 | repositories { 11 | jcenter() 12 | } 13 | 14 | dependencies { 15 | compile 'com.google.code.gson:gson:2.8.7' 16 | testCompile 'junit:junit:4.13.2' 17 | } 18 | 19 | if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { 20 | 21 | task javadocJar(type: Jar) { 22 | classifier = 'javadoc' 23 | from javadoc 24 | } 25 | 26 | task sourcesJar(type: Jar) { 27 | classifier = 'sources' 28 | from sourceSets.main.allSource 29 | } 30 | 31 | artifacts { 32 | archives javadocJar, sourcesJar 33 | } 34 | 35 | signing { 36 | sign configurations.archives 37 | } 38 | 39 | uploadArchives { 40 | repositories { 41 | mavenDeployer { 42 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 43 | 44 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 45 | authentication(userName: ossrhUsername, password: ossrhPassword) 46 | } 47 | 48 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 49 | authentication(userName: ossrhUsername, password: ossrhPassword) 50 | } 51 | 52 | pom.project { 53 | name 'json-patch' 54 | packaging 'jar' 55 | description 'A Gson-based JSON patch standard implementation.' 56 | url 'https://github.com/tananaev/json-patch' 57 | 58 | scm { 59 | connection 'scm:git:https://github.com/tananaev/json-patch' 60 | developerConnection 'scm:git:git@github.com:tananaev/json-patch.git' 61 | url 'https://github.com/tananaev/json-patch' 62 | } 63 | 64 | licenses { 65 | license { 66 | name 'Apache License, Version 2.0' 67 | url 'http://www.apache.org/licenses/LICENSE-2.0' 68 | } 69 | } 70 | 71 | developers { 72 | developer { 73 | id 'tananaev' 74 | name 'Anton Tananaev' 75 | email 'anton.tananaev@gmail.com' 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tananaev/json-patch/338aa68a1d570cc5dcc46367ddb225d613f372b8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'json-patch' 2 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/JsonPatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | import com.google.gson.GsonBuilder; 20 | import com.google.gson.JsonElement; 21 | import com.tananaev.jsonpatch.gson.JsonPathSerializer; 22 | import com.tananaev.jsonpatch.operation.AbsOperation; 23 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 24 | 25 | import java.util.LinkedList; 26 | 27 | public class JsonPatch extends LinkedList { 28 | 29 | public JsonElement apply(JsonElement original) { 30 | JsonElement result = original.deepCopy(); 31 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(result); 32 | for ( AbsOperation operation: this){ 33 | operation.applyInPlace(inPlaceElement); 34 | } 35 | return inPlaceElement.getJsonElement(); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | GsonBuilder gsonBuilder = new GsonBuilder(); 41 | gsonBuilder.registerTypeAdapter(JsonPath.class, new JsonPathSerializer()); 42 | return gsonBuilder.create().toJson(this); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/JsonPatchException.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | public class JsonPatchException extends RuntimeException { 20 | 21 | public JsonPatchException() { 22 | } 23 | 24 | public JsonPatchException(String message) { 25 | super(message); 26 | } 27 | 28 | 29 | public JsonPatchException(Throwable cause) { 30 | super(cause); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/JsonPatchFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | import com.google.gson.JsonArray; 20 | import com.google.gson.JsonElement; 21 | import com.google.gson.JsonObject; 22 | import com.tananaev.jsonpatch.operation.MoveOperation; 23 | import com.tananaev.jsonpatch.operation.RemoveOperation; 24 | import com.tananaev.jsonpatch.operation.ReplaceOperation; 25 | import com.tananaev.jsonpatch.operation.AddOperation; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.SortedSet; 31 | import java.util.TreeSet; 32 | 33 | public class JsonPatchFactory { 34 | 35 | private LongestCommonSubsequenceFactory lcsf = new LongestCommonSubsequenceFactory(); 36 | 37 | public JsonPatch create( JsonElement elementA, JsonElement elementB ){ 38 | JsonPatch patch = new JsonPatch(); 39 | PostProcessor pp = new PostProcessor(); 40 | 41 | boolean loop = true; 42 | 43 | while ( loop ) { 44 | JsonElement temp = patch.apply( elementA ); 45 | loop = processPatch(patch, new JsonPath("/"), temp, elementB); 46 | } 47 | 48 | return patch; 49 | } 50 | 51 | private boolean processPatch( JsonPatch patch, JsonPath path, JsonElement elementA, JsonElement elementB ){ 52 | if ( elementA == null ){ 53 | patch.add( new ReplaceOperation(path, elementB)); 54 | return true; 55 | } 56 | 57 | if ( elementA.equals(elementB) ){ 58 | return false; 59 | } 60 | 61 | if ( elementA.isJsonArray() && elementB.isJsonArray() ){ 62 | return processArrayPatch( patch, path, elementA.getAsJsonArray(), elementB.getAsJsonArray() ); 63 | } else if ( elementA.isJsonObject() && elementB.isJsonObject() ){ 64 | return processObjectPatch(patch, path, elementA.getAsJsonObject(), elementB.getAsJsonObject()); 65 | } else { 66 | patch.add( new ReplaceOperation(path, elementB)); 67 | return true; 68 | } 69 | } 70 | 71 | private boolean processObjectPatch( JsonPatch patch, JsonPath path, JsonObject elementA, JsonObject elementB ) { 72 | 73 | SortedSet elementAProps = extractProps(elementA); 74 | SortedSet elementBProps = extractProps(elementB); 75 | 76 | for ( String prop : elementAProps ){ 77 | JsonElement aValue = elementA.get(prop); 78 | if ( elementB.has( prop ) ){ 79 | elementBProps.remove(prop); 80 | JsonElement bValue = elementB.get(prop); 81 | if (!aValue.equals(bValue)) { 82 | // item exist in both but do not match 83 | return processPatch( patch, path.append(prop), aValue, bValue ); 84 | } 85 | } else { 86 | //item in original but not in target 87 | 88 | //look for move 89 | for ( String bProp: elementBProps ){ 90 | if ( elementB.get(bProp).equals(aValue) ){ 91 | patch.addLast(new MoveOperation(path.append(prop), path.append(bProp))); 92 | return true; 93 | } 94 | } 95 | 96 | patch.addLast(new RemoveOperation(path.append(prop))); 97 | return true; 98 | } 99 | } 100 | 101 | if ( elementBProps.size() != 0 ){ 102 | String prop = elementBProps.first(); 103 | //look for copy 104 | patch.addLast(new AddOperation(path.append(prop), elementB.get(prop))); 105 | return true; 106 | } 107 | throw new JsonPatchException("theoretically Unreachable"); 108 | } 109 | 110 | private boolean processArrayPatch( JsonPatch patch, JsonPath path, JsonArray elementA, JsonArray elementB ) { 111 | List listA = convertToList(elementA); 112 | List listB = convertToList(elementB); 113 | 114 | if ( listA.isEmpty() ){ 115 | patch.add( new AddOperation(path.append("-"),listB.get(0) )); 116 | return true; 117 | } 118 | 119 | List common = lcsf.search(listA, listB); 120 | 121 | int startOfCommonInA = lcsf.findStartIndex(common, listA); 122 | int startOfCommonInB = lcsf.findStartIndex(common, listB); 123 | 124 | if ( listB.size() == common.size() ){ 125 | if ( startOfCommonInA != 0 ){ 126 | patch.addLast(new RemoveOperation(path.append(0))); 127 | return true; 128 | } else { 129 | patch.addLast(new RemoveOperation(path.append(startOfCommonInA+common.size()))); 130 | return true; 131 | } 132 | } 133 | 134 | if ( startOfCommonInB != 0 ){ 135 | int targetPos = startOfCommonInB - 1; 136 | int targetPosA = startOfCommonInA; 137 | return expandCommon(patch, path, listA, listB, common, startOfCommonInA, targetPos, targetPosA); 138 | } else { 139 | int targetPos = startOfCommonInB + common.size(); 140 | int targetPosA = startOfCommonInA + common.size(); 141 | return expandCommon(patch, path, listA, listB, common, startOfCommonInA, targetPos, targetPosA); 142 | } 143 | 144 | } 145 | 146 | private SortedSet extractProps(JsonObject elementA) { 147 | SortedSet result = new TreeSet(); 148 | for ( Map.Entry entry : elementA.entrySet() ){ 149 | result.add(entry.getKey()); 150 | } 151 | return result; 152 | } 153 | 154 | private boolean expandCommon(JsonPatch patch, JsonPath path, List listA, List listB, List common, int startOfCommonInA, int targetPos, int targetPosA) { 155 | JsonElement target = listB.get(targetPos); 156 | 157 | if ( listA.size() > targetPosA ) { 158 | if (!listB.contains(listA.get(targetPosA))) { 159 | patch.addLast(new RemoveOperation(path.append(targetPosA))); 160 | return true; 161 | } 162 | } 163 | 164 | for ( int occurance : findOccurnacesIn(target, listA )){ 165 | if ( occurance >= startOfCommonInA+common.size() ) { 166 | patch.addLast(new MoveOperation(path.append(occurance), path.append(targetPosA))); 167 | return true; 168 | } 169 | if ( occurance < startOfCommonInA ) { 170 | 171 | int to = (targetPosA < occurance ) ? targetPosA -1 : targetPosA ; 172 | 173 | patch.addLast(new MoveOperation( 174 | path.append( occurance ), 175 | path.append( (to >= listA.size())? "-": Integer.toString(to)))); 176 | return true; 177 | } 178 | } 179 | patch.addLast(new AddOperation(path.append(targetPosA), target)); 180 | return true; 181 | } 182 | 183 | 184 | private List findOccurnacesIn(T item, List list){ 185 | List result = new ArrayList<>(); 186 | for ( int c = 0; c < list.size(); c++){ 187 | 188 | if ( list.get(c).equals(item) ){ 189 | result.add(c); 190 | } 191 | } 192 | return result; 193 | } 194 | 195 | private List convertToList(JsonArray array){ 196 | List result = new ArrayList<>(); 197 | 198 | for ( JsonElement element: array){ 199 | result.add(element); 200 | } 201 | 202 | return result; 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/JsonPath.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | import com.google.gson.JsonElement; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | public class JsonPath { 26 | 27 | private List path; 28 | 29 | public JsonPath(String path) { 30 | this.path = new ArrayList<>(Arrays.asList(path.split("/"))); 31 | if ( this.path.size() > 0 ) { 32 | this.path.remove(0); 33 | } 34 | } 35 | 36 | private JsonPath(List path) { 37 | this.path = new ArrayList<>(path); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | if ( path.size() == 0 ){ 43 | return "/"; 44 | } 45 | StringBuilder sb = new StringBuilder(); 46 | for ( String s: path ){ 47 | sb.append("/").append(s); 48 | } 49 | 50 | return sb.toString(); 51 | } 52 | 53 | public String tail(){ 54 | if ( path.size() == 0 ){ 55 | return null; 56 | } 57 | return path.get(path.size()-1); 58 | } 59 | 60 | public JsonPath head(){ 61 | if ( path.size() == 0){ 62 | return this; 63 | } 64 | return new JsonPath(path.subList(0, path.size()-1)); 65 | } 66 | 67 | @Override 68 | public boolean equals(Object obj) { 69 | if ( obj == null ){ 70 | return false; 71 | } else if ( !( obj instanceof JsonPath ) ){ 72 | return false; 73 | } else if ( ((JsonPath)obj).path.size() != path.size()){ 74 | return false; 75 | } else { 76 | JsonPath other = (JsonPath) obj; 77 | boolean result = true; 78 | for ( int c = 0; c < path.size(); c++ ){ 79 | result = result && path.get(c).equals( other.path.get(c)); 80 | } 81 | return result; 82 | } 83 | } 84 | 85 | public JsonPath append(String path) { 86 | JsonPath result = new JsonPath(this.path); 87 | result.path.addAll(Arrays.asList(path.split("/"))); 88 | return result; 89 | } 90 | 91 | public JsonPath append(int path) { 92 | return append(Integer.toString(path)); 93 | } 94 | 95 | public JsonElement navigate(JsonElement original) { 96 | for ( String segment: path ){ 97 | if ( original.isJsonObject() ){ 98 | original = original.getAsJsonObject().get(segment); 99 | } else if ( original.isJsonArray() ) { 100 | original = original.getAsJsonArray().get(Integer.parseInt(segment)); 101 | } 102 | } 103 | return original; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/LongestCommonSubsequenceFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | import java.util.List; 20 | 21 | public class LongestCommonSubsequenceFactory { 22 | 23 | public List search(List seqA, List seqB) { 24 | if ( seqA.size() > seqB.size()){ 25 | return subsearch( seqB, seqA ); 26 | } else { 27 | return subsearch( seqA, seqB ); 28 | } 29 | } 30 | 31 | private List subsearch(List shortList, List longList) { 32 | if ( shortList.isEmpty() ){ 33 | return shortList; 34 | } 35 | 36 | int delta = longList.size() - shortList.size(); 37 | 38 | for( int c = 0; c <= delta; c++ ){ 39 | if ( checkEquals( shortList, longList, c)){ 40 | return shortList; 41 | } 42 | } 43 | //remove one from right 44 | List rightList = subsearch(shortList.subList(0, shortList.size() - 1), longList); 45 | //remove one from left 46 | List leftList = subsearch(shortList.subList(1, shortList.size()), longList); 47 | if ( leftList.size() > rightList.size() ){ 48 | return leftList; 49 | } else { 50 | return rightList; 51 | } 52 | } 53 | 54 | private boolean checkEquals(List shortList, List longList, int index) { 55 | boolean result = true; 56 | for ( int c = 0; c < shortList.size(); c++ ){ 57 | result = result && shortList.get(c).equals(longList.get(c+index)); 58 | } 59 | return result; 60 | } 61 | 62 | public int findStartIndex( List sub, List full ){ 63 | for( int c = 0; c < full.size(); c++ ) { 64 | if (checkEquals(sub, full, c)) { 65 | return c; 66 | } 67 | } 68 | return -1; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/PostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch; 18 | 19 | import com.tananaev.jsonpatch.operation.MoveOperation; 20 | import com.tananaev.jsonpatch.operation.RemoveOperation; 21 | import com.tananaev.jsonpatch.operation.AbsOperation; 22 | 23 | public class PostProcessor { 24 | 25 | public void process(JsonPatch patch) { 26 | 27 | if (patch.isEmpty()){ 28 | return; 29 | } 30 | 31 | AbsOperation operation = patch.getLast(); 32 | 33 | if (operation.getOperationName().equals("remove")) { 34 | if (isNumeric(operation.path.tail())) { 35 | //unwindMoves(patch); 36 | } 37 | } 38 | } 39 | 40 | private void unwindMoves(JsonPatch patch) { 41 | 42 | RemoveOperation removeOperation = (RemoveOperation) patch.removeLast(); 43 | 44 | while (!patch.isEmpty()){ 45 | AbsOperation priorOperation = patch.removeLast(); 46 | if (priorOperation.getOperationName().equals("move")) { 47 | if (priorOperation.path.head().equals(removeOperation.path.head())) { 48 | //operating on same element 49 | MoveOperation priorMove = (MoveOperation) priorOperation; 50 | if (priorMove.path.head().equals(priorMove.from.head())) { 51 | //move is within local element 52 | try { 53 | int removeIndex = Integer.parseInt(removeOperation.path.tail()); 54 | int moveToIndex = Integer.parseInt(priorMove.path.tail()); 55 | int moveFromIndex = Integer.parseInt(priorMove.from.tail()); 56 | 57 | if (moveToIndex < moveFromIndex) { 58 | if (removeIndex - moveToIndex == 1) { 59 | removeOperation.path = priorMove.path; 60 | } 61 | } else { 62 | patch.addLast(priorOperation); 63 | break; 64 | } 65 | } catch ( NumberFormatException e){ 66 | patch.addLast(priorOperation); 67 | break; 68 | } 69 | } else { 70 | patch.addLast(priorOperation); 71 | break; 72 | } 73 | } else { 74 | patch.addLast(priorOperation); 75 | break; 76 | } 77 | } else { 78 | patch.addLast(priorOperation); 79 | break; 80 | } 81 | } 82 | 83 | patch.addLast(removeOperation); 84 | 85 | } 86 | 87 | public static boolean isNumeric(String str){ 88 | try{ 89 | Integer.parseInt(str); 90 | } catch(NumberFormatException nfe) { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/gson/AbsOperationDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.gson; 18 | 19 | import com.google.gson.*; 20 | import com.tananaev.jsonpatch.operation.*; 21 | 22 | import java.lang.reflect.Type; 23 | 24 | public class AbsOperationDeserializer implements JsonDeserializer { 25 | 26 | @Override 27 | public AbsOperation deserialize(JsonElement element, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 28 | String operation = element.getAsJsonObject().getAsJsonPrimitive("op").getAsString(); 29 | return jsonDeserializationContext.deserialize(element, getType(operation)); 30 | } 31 | 32 | private Type getType(String operation) throws JsonSyntaxException { 33 | if ("add".equals(operation)) return AddOperation.class; 34 | if ("move".equals(operation)) return MoveOperation.class; 35 | if ("remove".equals(operation)) return RemoveOperation.class; 36 | if ("replace".equals(operation)) return ReplaceOperation.class; 37 | throw new JsonSyntaxException("operation " + operation + " not supported"); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/gson/JsonPathDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.gson; 18 | 19 | import com.google.gson.JsonDeserializationContext; 20 | import com.google.gson.JsonDeserializer; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonParseException; 23 | import com.tananaev.jsonpatch.JsonPath; 24 | 25 | import java.lang.reflect.Type; 26 | 27 | public class JsonPathDeserializer implements JsonDeserializer { 28 | 29 | @Override 30 | public JsonPath deserialize(JsonElement element, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { 31 | return new JsonPath(element.getAsJsonPrimitive().getAsString()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/gson/JsonPathSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.gson; 18 | 19 | import com.google.gson.JsonElement; 20 | import com.google.gson.JsonPrimitive; 21 | import com.google.gson.JsonSerializationContext; 22 | import com.google.gson.JsonSerializer; 23 | import com.tananaev.jsonpatch.JsonPath; 24 | 25 | import java.lang.reflect.Type; 26 | 27 | public class JsonPathSerializer implements JsonSerializer { 28 | 29 | @Override 30 | public JsonElement serialize(JsonPath jsonPath, Type type, JsonSerializationContext jsonSerializationContext) { 31 | return new JsonPrimitive(jsonPath.toString()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/AbsOperation.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.annotations.SerializedName; 5 | import com.tananaev.jsonpatch.JsonPath; 6 | 7 | public abstract class AbsOperation { 8 | 9 | @SerializedName("op") 10 | private String operationName; 11 | 12 | public JsonPath path; 13 | 14 | public AbsOperation(){ 15 | operationName = getOperationName(); 16 | } 17 | 18 | public abstract String getOperationName(); 19 | 20 | /** 21 | * Applies the operation on the source and returns the patched object. The source is unmodified. 22 | * @param sourceElement to which the patch is to be applied 23 | * @return final json after patch is applied 24 | */ 25 | public JsonElement apply( JsonElement sourceElement ){ 26 | JsonElement copiedSource = sourceElement.deepCopy(); 27 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(copiedSource); 28 | applyInPlace(inPlaceElement); 29 | return inPlaceElement.getJsonElement(); 30 | }; 31 | 32 | /** 33 | * An optimised version of apply where the source is not copied and is directly modified if possible. 34 | * Updates the inPlaceElement to contain the patched json element. 35 | * Refer to {@link InPlaceElementWrapper} for details on why a wrapper is needed 36 | * @param inPlaceElement input to which the patch is to be applied 37 | */ 38 | public abstract void applyInPlace(InPlaceElementWrapper inPlaceElement ); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/AddOperation.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.annotations.SerializedName; 6 | import com.tananaev.jsonpatch.JsonPath; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Iterator; 10 | import java.util.List; 11 | 12 | public class AddOperation extends AbsOperation { 13 | 14 | @SerializedName("value") 15 | public JsonElement data; 16 | 17 | public AddOperation(JsonPath path, JsonElement data) { 18 | this.data = data; 19 | this.path = path; 20 | } 21 | 22 | @Override 23 | public String getOperationName() { 24 | return "add"; 25 | } 26 | 27 | @Override 28 | public void applyInPlace(InPlaceElementWrapper inPlaceElement){ 29 | JsonElement sourceElement = inPlaceElement.getJsonElement(); 30 | JsonElement item = path.head().navigate(sourceElement); 31 | 32 | if ( item.isJsonObject() ){ 33 | item.getAsJsonObject().add(path.tail(),data); 34 | } else if ( item.isJsonArray() ){ 35 | 36 | JsonArray array = item.getAsJsonArray(); 37 | 38 | int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail()); 39 | 40 | List temp = new ArrayList(); 41 | 42 | Iterator iter = array.iterator(); 43 | while (iter.hasNext()){ 44 | JsonElement stuff = iter.next(); 45 | iter.remove(); 46 | temp.add( stuff ); 47 | } 48 | 49 | temp.add(index, data); 50 | 51 | for ( JsonElement stuff: temp ){ 52 | array.add(stuff); 53 | } 54 | 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/InPlaceElementWrapper.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | 5 | /** 6 | * Wrapper class over json element to pass as an input for in place operations. 7 | * This is needed because we cannot convert actual JsonElement types into another in place 8 | * i.e, we cannot convert a JsonPrimitive (or other) into a JsonObject (or other) in-place 9 | * if needed by the patch operation. 10 | */ 11 | public class InPlaceElementWrapper { 12 | 13 | private JsonElement jsonElement; 14 | 15 | public JsonElement getJsonElement() { 16 | return jsonElement; 17 | } 18 | 19 | public InPlaceElementWrapper(JsonElement element) { 20 | this.jsonElement = element; 21 | } 22 | 23 | public void setJsonElement(JsonElement jsonElement) { 24 | this.jsonElement = jsonElement; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/MoveOperation.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.tananaev.jsonpatch.JsonPath; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | 11 | public class MoveOperation extends AbsOperation{ 12 | 13 | public final JsonPath from; 14 | 15 | public MoveOperation(JsonPath from, JsonPath path) { 16 | this.path = path; 17 | this.from = from; 18 | } 19 | 20 | @Override 21 | public String getOperationName() { 22 | return "move"; 23 | } 24 | 25 | @Override 26 | public void applyInPlace(InPlaceElementWrapper inPlaceElement) { 27 | JsonElement sourceElement = inPlaceElement.getJsonElement(); 28 | JsonElement value = from.navigate(sourceElement); 29 | 30 | JsonElement existingElement = from.head().navigate(sourceElement); 31 | JsonElement destination = path.head().navigate(sourceElement); 32 | 33 | if ( existingElement.isJsonObject() ){ 34 | existingElement.getAsJsonObject().remove(from.tail()); 35 | } else if ( existingElement.isJsonArray() ){ 36 | JsonArray array = existingElement.getAsJsonArray(); 37 | 38 | int index = (from.tail().equals("-")) ? array.size() : Integer.valueOf(from.tail()); 39 | 40 | array.remove(index); 41 | } 42 | 43 | if ( destination.isJsonObject() ){ 44 | destination.getAsJsonObject().add(path.tail(),value); 45 | } else if ( destination.isJsonArray() ){ 46 | 47 | JsonArray array = destination.getAsJsonArray(); 48 | 49 | int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail()); 50 | 51 | List temp = new ArrayList(); 52 | 53 | Iterator iter = array.iterator(); 54 | while (iter.hasNext()){ 55 | JsonElement stuff = iter.next(); 56 | iter.remove(); 57 | temp.add( stuff ); 58 | } 59 | 60 | temp.add(index, value); 61 | 62 | for ( JsonElement stuff: temp ){ 63 | array.add(stuff); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/RemoveOperation.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.tananaev.jsonpatch.JsonPath; 6 | 7 | public class RemoveOperation extends AbsOperation{ 8 | 9 | public RemoveOperation( JsonPath path ) { 10 | this.path = path; 11 | } 12 | 13 | @Override 14 | public String getOperationName() { 15 | return "remove"; 16 | } 17 | 18 | @Override 19 | public void applyInPlace(InPlaceElementWrapper inPlaceElement) { 20 | 21 | JsonElement item = path.head().navigate(inPlaceElement.getJsonElement()); 22 | 23 | if ( item.isJsonObject() ){ 24 | item.getAsJsonObject().remove(path.tail()); 25 | } else if ( item.isJsonArray() ){ 26 | JsonArray array = item.getAsJsonArray(); 27 | 28 | int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail()); 29 | 30 | array.remove(index); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/tananaev/jsonpatch/operation/ReplaceOperation.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.operation; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.annotations.SerializedName; 7 | import com.tananaev.jsonpatch.JsonPath; 8 | 9 | public class ReplaceOperation extends AbsOperation { 10 | 11 | @SerializedName("value") 12 | public JsonElement data; 13 | 14 | public ReplaceOperation(JsonPath path, JsonElement data) { 15 | this.path = path; 16 | this.data = data; 17 | } 18 | 19 | @Override 20 | public String getOperationName() { 21 | return "replace"; 22 | } 23 | 24 | @Override 25 | public void applyInPlace(InPlaceElementWrapper inPlaceElement) { 26 | 27 | JsonElement item = path.head().navigate(inPlaceElement.getJsonElement()); 28 | 29 | if ( item.isJsonObject() ){ 30 | JsonObject object = item.getAsJsonObject(); 31 | 32 | object.add( path.tail(), data );; 33 | 34 | } else if ( item.isJsonArray() ){ 35 | 36 | JsonArray array = item.getAsJsonArray(); 37 | 38 | int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail()); 39 | 40 | if ( index < array.size() ) { 41 | array.set(index, data); 42 | } else { 43 | array.add(data); 44 | } 45 | 46 | } else { 47 | inPlaceElement.setJsonElement(data); 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/JsonPatchTest.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonPrimitive; 7 | import com.tananaev.jsonpatch.gson.AbsOperationDeserializer; 8 | import com.tananaev.jsonpatch.gson.JsonPathDeserializer; 9 | import com.tananaev.jsonpatch.operation.AbsOperation; 10 | import com.tananaev.jsonpatch.operation.ReplaceOperation; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | 14 | import java.io.InputStreamReader; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | public class JsonPatchTest { 19 | 20 | @Test 21 | public void toStringTest() throws Exception { 22 | JsonPatch patch = new JsonPatch(); 23 | patch.add(new ReplaceOperation(new JsonPath("/object/key"), new JsonPrimitive("test"))); 24 | assertEquals("[{\"value\":\"test\",\"op\":\"replace\",\"path\":\"/object/key\"}]", patch.toString()); 25 | } 26 | 27 | 28 | @Test 29 | public void applySmallTest() throws Exception { 30 | test("small"); 31 | } 32 | 33 | private void test(String directory) throws Exception { 34 | Gson gson = new GsonBuilder() 35 | .registerTypeAdapter(JsonPath.class, new JsonPathDeserializer()) 36 | .registerTypeAdapter(AbsOperation.class, new AbsOperationDeserializer()) 37 | .create(); 38 | 39 | JsonElement origin = gson.fromJson(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(directory + "/origin.json")), JsonElement.class); 40 | JsonPatch patch = gson.fromJson(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(directory + "/patch.json")), JsonPatch.class); 41 | JsonElement reference = gson.fromJson(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(directory + "/result.json")), JsonElement.class); 42 | 43 | JsonElement patched = patch.apply(origin); 44 | 45 | Assert.assertEquals(reference, patched); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/JsonPatchFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.GsonBuilder; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.reflect.TypeToken; 23 | import com.tananaev.jsonpatch.JsonPatchFactory; 24 | import com.tananaev.jsonpatch.JsonPath; 25 | import com.tananaev.jsonpatch.gson.JsonPathDeserializer; 26 | import com.tananaev.jsonpatch.gson.JsonPathSerializer; 27 | import com.tananaev.jsonpatch.test.util.JsonPatchTestCase; 28 | import com.tananaev.jsonpatch.JsonPatch; 29 | import org.junit.Assert; 30 | import org.junit.Test; 31 | import org.junit.runner.RunWith; 32 | import org.junit.runners.Parameterized; 33 | import org.junit.runners.Parameterized.Parameters; 34 | 35 | import java.io.BufferedReader; 36 | import java.io.InputStreamReader; 37 | import java.util.Collection; 38 | import java.util.LinkedList; 39 | import java.util.List; 40 | 41 | @RunWith(Parameterized.class) 42 | public class JsonPatchFactoryTest { 43 | 44 | private final JsonPatchTestCase testCase; 45 | private final Gson gson = new GsonBuilder() 46 | .setPrettyPrinting() 47 | .registerTypeAdapter(JsonPath.class, new JsonPathDeserializer()) 48 | .registerTypeAdapter(JsonPath.class, new JsonPathSerializer()) 49 | .create(); 50 | 51 | @Parameters 52 | public static Collection addedNumbers() { 53 | Gson gson = new Gson(); 54 | List cases = gson.fromJson( 55 | new BufferedReader(new InputStreamReader(JsonPatchFactoryTest.class.getResourceAsStream("/test_cases.json"))), 56 | new TypeToken>() {}.getType()); 57 | 58 | LinkedList temp = new LinkedList(); 59 | for ( JsonPatchTestCase singleCase: cases ) { 60 | JsonPatchTestCase[] temp2 = new JsonPatchTestCase[1]; 61 | temp2[0] = singleCase; 62 | temp.add(temp2); 63 | } 64 | 65 | return temp; 66 | } 67 | 68 | public JsonPatchFactoryTest( JsonPatchTestCase testCase ) { 69 | this.testCase = testCase; 70 | } 71 | 72 | @Test 73 | public void runCase(){ 74 | 75 | JsonPatchFactory jpf = new JsonPatchFactory(); 76 | JsonPatch patch = jpf.create(testCase.first, testCase.second); 77 | 78 | JsonElement result = patch.apply(testCase.first); 79 | 80 | Assert.assertEquals(testCase.second, result); 81 | 82 | System.out.println(String.format("running ...\nA: '%s'\nB: '%s'\nC: '%s'\npatch\n%s\n--------------\n\n", testCase.first, testCase.second, result, gson.toJson(patch))); 83 | System.out.flush(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/JsonPathTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.GsonBuilder; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonParser; 23 | import com.google.gson.reflect.TypeToken; 24 | import com.tananaev.jsonpatch.JsonPath; 25 | import com.tananaev.jsonpatch.gson.JsonPathDeserializer; 26 | import com.tananaev.jsonpatch.gson.JsonPathSerializer; 27 | import org.junit.Assert; 28 | import org.junit.Test; 29 | 30 | import java.util.List; 31 | 32 | public class JsonPathTest { 33 | 34 | @Test 35 | public void canParseRoot(){ 36 | JsonPath path = new JsonPath("/"); 37 | 38 | Assert.assertEquals("/", path.toString()); 39 | 40 | Assert.assertEquals(null, path.tail()); 41 | 42 | Assert.assertEquals("/", path.head().toString()); 43 | } 44 | 45 | 46 | @Test 47 | public void canParseMore(){ 48 | JsonPath path = new JsonPath("/a/0/b"); 49 | 50 | Assert.assertEquals("/a/0/b", path.toString()); 51 | 52 | Assert.assertEquals("b", path.tail()); 53 | 54 | Assert.assertEquals("/a/0", path.head().toString()); 55 | } 56 | 57 | @Test 58 | public void canAppend(){ 59 | JsonPath path = new JsonPath("/a/0/b"); 60 | 61 | Assert.assertEquals("/a/0/b/c", path.append("c").toString()); 62 | 63 | Assert.assertEquals("/a/0/b/d/e", path.append("d/e").toString()); 64 | } 65 | 66 | @Test 67 | public void equalsIsSane(){ 68 | Assert.assertEquals(new JsonPath("/"), new JsonPath("/") ); 69 | Assert.assertEquals(new JsonPath("/a/0/b"), new JsonPath("/a/0/b") ); 70 | Assert.assertNotEquals(new JsonPath("/"), new JsonPath("/a/0/b")); 71 | Assert.assertNotEquals(new JsonPath("/"), null); 72 | } 73 | 74 | @Test 75 | public void gsonIsSane(){ 76 | Gson gson = new GsonBuilder() 77 | .registerTypeAdapter(JsonPath.class, new JsonPathDeserializer()) 78 | .registerTypeAdapter(JsonPath.class, new JsonPathSerializer()) 79 | .create(); 80 | 81 | List paths = gson.fromJson("[\"/a/0/b/1\"]", new TypeToken>(){}.getType()); 82 | 83 | Assert.assertNotNull(paths); 84 | Assert.assertEquals(1, paths.size() ); 85 | Assert.assertEquals("/a/0/b/1", paths.get(0).toString()); 86 | } 87 | 88 | @Test 89 | public void canNavigateRoot(){ 90 | JsonPath path = new JsonPath("/"); 91 | JsonElement element = new JsonParser().parse("{\"a\":{}}"); 92 | 93 | Assert.assertEquals(element, path.navigate(element)); 94 | } 95 | 96 | @Test 97 | public void canNavigateObject(){ 98 | JsonPath path = new JsonPath("/a"); 99 | JsonParser parser = new JsonParser(); 100 | JsonElement element = parser.parse("{\"a\":{\"b\":\"stuff\"}}"); 101 | 102 | Assert.assertEquals(parser.parse("{\"b\":\"stuff\"}"), path.navigate(element)); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/LongestCommonSubsequenceFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.GsonBuilder; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.tananaev.jsonpatch.LongestCommonSubsequenceFactory; 23 | import com.tananaev.jsonpatch.test.util.LCSFTestCase; 24 | import org.junit.Assert; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.Parameterized; 28 | 29 | import java.io.BufferedReader; 30 | import java.io.InputStreamReader; 31 | import java.util.Collection; 32 | import java.util.LinkedList; 33 | import java.util.List; 34 | 35 | @RunWith(Parameterized.class) 36 | public class LongestCommonSubsequenceFactoryTest { 37 | 38 | private final LCSFTestCase testCase; 39 | private final Gson gson = new GsonBuilder() 40 | .setPrettyPrinting() 41 | .create(); 42 | 43 | @Parameterized.Parameters(name = "{index} {1}") 44 | public static Collection addedNumbers() { 45 | Gson gson = new Gson(); 46 | List cases = gson.fromJson( 47 | new BufferedReader(new InputStreamReader(JsonPatchFactoryTest.class.getResourceAsStream("/sublist.json"))), 48 | new TypeToken>() {}.getType()); 49 | 50 | LinkedList temp = new LinkedList(); 51 | for ( LCSFTestCase singleCase: cases ) { 52 | Object[] temp2 = new Object[2]; 53 | temp2[0] = singleCase; 54 | temp2[1] = singleCase.name; 55 | temp.add(temp2); 56 | } 57 | 58 | return temp; 59 | } 60 | 61 | public LongestCommonSubsequenceFactoryTest( LCSFTestCase testCase, String name ) { 62 | this.testCase = testCase; 63 | } 64 | 65 | @Test 66 | public void runCase(){ 67 | LongestCommonSubsequenceFactory lcsf = new LongestCommonSubsequenceFactory(); 68 | 69 | List result = lcsf.search(testCase.A, testCase.B); 70 | 71 | Assert.assertTrue(String.format("expected: %s, got: %s", testCase.expected, result), checkEquals(result, testCase.expected)); 72 | } 73 | 74 | private boolean checkEquals(List listA, List listB) { 75 | if( listA.size() == 0 && listB.size() == 0){ 76 | return true; 77 | } 78 | if ( listA.size() == 0 ){ 79 | return false; 80 | } 81 | if ( listB.size() == 0 ){ 82 | return false; 83 | } 84 | boolean result = true; 85 | for ( int c = 0; c < listA.size(); c++ ){ 86 | result = result && listA.get(c).equals(listB.get(c)); 87 | } 88 | return result; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/OperationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test; 18 | 19 | import com.google.gson.JsonElement; 20 | import com.google.gson.JsonNull; 21 | import com.google.gson.JsonObject; 22 | import com.google.gson.JsonPrimitive; 23 | import com.tananaev.jsonpatch.JsonPath; 24 | import com.tananaev.jsonpatch.operation.AddOperation; 25 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 26 | import com.tananaev.jsonpatch.operation.ReplaceOperation; 27 | import org.junit.Assert; 28 | import org.junit.Test; 29 | 30 | public class OperationTest { 31 | 32 | 33 | @Test 34 | public void replaceRootNullWithPrimitive(){ 35 | JsonElement element = JsonNull.INSTANCE; 36 | 37 | ReplaceOperation operation = new ReplaceOperation( new JsonPath("/"), new JsonPrimitive(1) ); 38 | 39 | JsonElement result = operation.apply(element); 40 | 41 | Assert.assertEquals("1", result.toString()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/operation/AddOperationTest.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.test.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.tananaev.jsonpatch.JsonPath; 6 | import com.tananaev.jsonpatch.operation.AddOperation; 7 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class AddOperationTest { 12 | 13 | @Test 14 | public void basicAddWorks(){ 15 | JsonElement element = new JsonObject(); 16 | String originalData = element.toString(); 17 | 18 | JsonElement result = new AddOperation(new JsonPath("/a"), new JsonObject() ).apply(element); 19 | 20 | Assert.assertEquals(originalData, element.toString()); 21 | Assert.assertEquals("{\"a\":{}}", result.toString()); 22 | } 23 | 24 | @Test 25 | public void basicInPlaceAddWorks(){ 26 | JsonElement element = new JsonObject(); 27 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(element); 28 | AddOperation addOperation = new AddOperation(new JsonPath("/a"), new JsonObject()); 29 | 30 | addOperation.applyInPlace(inPlaceElement); 31 | JsonElement result = inPlaceElement.getJsonElement(); 32 | 33 | Assert.assertEquals("{\"a\":{}}", result.toString()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/operation/MoveOperationTest.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.test.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonPrimitive; 6 | import com.tananaev.jsonpatch.JsonPath; 7 | import com.tananaev.jsonpatch.operation.AddOperation; 8 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 9 | import com.tananaev.jsonpatch.operation.MoveOperation; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class MoveOperationTest { 14 | 15 | @Test 16 | public void basicMoveWorks(){ 17 | JsonElement element = new JsonObject(); 18 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 19 | String originalData = element.toString(); 20 | 21 | JsonElement result = new MoveOperation(new JsonPath("/a"), new JsonPath("/b") ).apply(element); 22 | 23 | Assert.assertEquals(originalData, element.toString()); 24 | Assert.assertEquals("{\"b\":\"123\"}", result.toString()); 25 | } 26 | 27 | @Test 28 | public void basicInPlaceMoveWorks(){ 29 | 30 | JsonElement element = new JsonObject(); 31 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 32 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(element); 33 | 34 | MoveOperation moveOperation = new MoveOperation(new JsonPath("/a"), new JsonPath("/b") ); 35 | moveOperation.applyInPlace(inPlaceElement); 36 | 37 | Assert.assertEquals("{\"b\":\"123\"}", inPlaceElement.getJsonElement().toString()); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/operation/RemoveOperationTest.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.test.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonPrimitive; 6 | import com.tananaev.jsonpatch.JsonPath; 7 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 8 | import com.tananaev.jsonpatch.operation.MoveOperation; 9 | import com.tananaev.jsonpatch.operation.RemoveOperation; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class RemoveOperationTest { 14 | 15 | @Test 16 | public void basicRemoveWorks(){ 17 | JsonElement element = new JsonObject(); 18 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 19 | String originalData = element.toString(); 20 | 21 | JsonElement result = new RemoveOperation(new JsonPath("/a") ).apply(element); 22 | 23 | Assert.assertEquals(originalData, element.toString()); 24 | Assert.assertEquals("{}", result.toString()); 25 | } 26 | 27 | @Test 28 | public void basicInPlaceRemoveWorks(){ 29 | 30 | JsonElement element = new JsonObject(); 31 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 32 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(element); 33 | 34 | RemoveOperation removeOperation = new RemoveOperation(new JsonPath("/a")); 35 | removeOperation.applyInPlace(inPlaceElement); 36 | 37 | Assert.assertEquals("{}", inPlaceElement.getJsonElement().toString()); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/operation/ReplaceOperationTest.java: -------------------------------------------------------------------------------- 1 | package com.tananaev.jsonpatch.test.operation; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonPrimitive; 6 | import com.tananaev.jsonpatch.JsonPath; 7 | import com.tananaev.jsonpatch.operation.InPlaceElementWrapper; 8 | import com.tananaev.jsonpatch.operation.RemoveOperation; 9 | import com.tananaev.jsonpatch.operation.ReplaceOperation; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class ReplaceOperationTest { 14 | @Test 15 | public void basicReplaceWorks(){ 16 | JsonElement element = new JsonObject(); 17 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 18 | String originalData = element.toString(); 19 | 20 | JsonElement newElement = new JsonPrimitive("1234"); 21 | 22 | JsonElement result = new ReplaceOperation(new JsonPath("/a"), newElement).apply(element); 23 | 24 | Assert.assertEquals(originalData, element.toString()); 25 | Assert.assertEquals("{\"a\":\"1234\"}", result.toString()); 26 | } 27 | 28 | @Test 29 | public void basicInPlaceReplaceWorks(){ 30 | 31 | JsonElement element = new JsonObject(); 32 | element.getAsJsonObject().add("a", new JsonPrimitive("123")); 33 | 34 | JsonElement newElement = new JsonPrimitive("1234"); 35 | InPlaceElementWrapper inPlaceElement = new InPlaceElementWrapper(element); 36 | 37 | ReplaceOperation replaceOperation = new ReplaceOperation(new JsonPath("/a"), newElement); 38 | replaceOperation.applyInPlace(inPlaceElement); 39 | 40 | Assert.assertEquals("{\"a\":\"1234\"}", inPlaceElement.getJsonElement().toString()); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/util/JsonPatchTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test.util; 18 | 19 | import com.google.gson.JsonElement; 20 | 21 | public class JsonPatchTestCase { 22 | public JsonElement first; 23 | public JsonElement second; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/tananaev/jsonpatch/test/util/LCSFTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 C. A. Fitzgerald 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.tananaev.jsonpatch.test.util; 18 | 19 | import java.util.List; 20 | 21 | public class LCSFTestCase { 22 | public List A; 23 | public List B; 24 | public String name; 25 | public List expected; 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/small/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "baz": "qux", 3 | "foo": "bar" 4 | } -------------------------------------------------------------------------------- /src/test/resources/small/patch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "op": "replace", "path": "/baz", "value": "boo" }, 3 | { "op": "add", "path": "/hello", "value": ["world"] }, 4 | { "op": "remove", "path": "/foo" } 5 | ] -------------------------------------------------------------------------------- /src/test/resources/small/result.json: -------------------------------------------------------------------------------- 1 | { 2 | "baz": "boo", 3 | "hello": ["world"] 4 | } -------------------------------------------------------------------------------- /src/test/resources/sublist.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "equal", 4 | "A": ["a","b","c","d","e","f","g"], 5 | "B": ["a","b","c","d","e","f","g"], 6 | "expected": ["a","b","c","d","e","f","g"] 7 | }, 8 | { 9 | "name": "half front", 10 | "A": ["a","b","c","d","e","f","g"], 11 | "B": ["a","b","c"], 12 | "expected": ["a","b","c"] 13 | }, 14 | { 15 | "name": "half back", 16 | "A": ["a","b","c","d","e","f","g"], 17 | "B": ["d","e","f","g"], 18 | "expected": ["d","e","f","g"] 19 | }, 20 | { 21 | "name": "half front inverted", 22 | "B": ["a","b","c","d","e","f","g"], 23 | "A": ["a","b","c"], 24 | "expected": ["a","b","c"] 25 | }, 26 | { 27 | "name": "half back inverted", 28 | "B": ["a","b","c","d","e","f","g"], 29 | "A": ["d","e","f","g"], 30 | "expected": ["d","e","f","g"] 31 | }, 32 | { 33 | "name": "opposing options", 34 | "A": ["a","b","c","X","e","f","g"], 35 | "B": ["a","b","c","d","e","f","g"], 36 | "expected": ["a","b","c"] 37 | }, 38 | { 39 | "name": "end", 40 | "A": ["a","b","c","d","e","f","g"], 41 | "B": ["e","f","g"], 42 | "expected": ["e","f","g"] 43 | }, 44 | { 45 | "name": "overlap", 46 | "A": ["a","b","c","d","e","f","g"], 47 | "B": ["a","b","c","q","b","c","d","e"], 48 | "expected": ["b","c","d","e"] 49 | } 50 | 51 | ] -------------------------------------------------------------------------------- /src/test/resources/test_cases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "first": {}, 4 | "second": {}, 5 | "patch": [] 6 | }, 7 | { 8 | "first": null, 9 | "second": 1, 10 | "patch": [ 11 | { "op": "replace", "path": "", "value": 1 } 12 | ] 13 | }, 14 | { 15 | "first": 0, 16 | "second": 0.0, 17 | "patch": [] 18 | }, 19 | { 20 | "first": {}, 21 | "second": { "a": "b" }, 22 | "patch": [ 23 | { "op": "add", "path": "/a", "value": "b" } 24 | ] 25 | }, 26 | { 27 | "first": { "a": "b" }, 28 | "second": {}, 29 | "patch": [ 30 | { "op": "remove", "path": "/a" } 31 | ] 32 | }, 33 | { 34 | "first": { "a": "b" }, 35 | "second": { "a": "c" }, 36 | "patch": [ 37 | { "op": "replace", "path": "/a", "value": "c" } 38 | ] 39 | }, 40 | { 41 | "first": [], 42 | "second": [ "a" ], 43 | "patch": [ 44 | { "op": "add", "path": "/-", "value": "a" } 45 | ] 46 | }, 47 | { 48 | "first": [], 49 | "second": [ "a", "b" ], 50 | "patch": [ 51 | { "op": "add", "path": "/-", "value": "a" }, 52 | { "op": "add", "path": "/-", "value": "b" } 53 | ] 54 | }, 55 | { 56 | "first": [ 1, 2, 3 ], 57 | "second": [ 1, 2 ], 58 | "patch": [ 59 | { "op": "remove", "path": "/2" } 60 | ] 61 | }, 62 | { 63 | "first": [ 1, 2, 3 ], 64 | "second": [ 1 ], 65 | "patch": [ 66 | { "op": "remove", "path": "/1" }, 67 | { "op": "remove", "path": "/1" } 68 | ] 69 | }, 70 | { 71 | "first": [ "hello", "world" ], 72 | "second": [ "hello", "world!" ], 73 | "patch": [ 74 | { "op": "replace", "path": "/1", "value": "world!" } 75 | ] 76 | }, 77 | { 78 | "first": { "a": "b", "c": [ "d" ] }, 79 | "second": { "a": "b", "c": [ "d", "e" ] }, 80 | "patch": [ 81 | { "op": "add", "path": "/c/-", "value": "e" } 82 | ] 83 | }, 84 | { 85 | "first": [ 1, 2, 3, 4 ], 86 | "second": [ 0, 2.0, 3 ], 87 | "patch": [ 88 | { "op": "replace", "path": "/0", "value": 0 }, 89 | { "op": "remove", "path": "/3" } 90 | ] 91 | }, 92 | { 93 | "first": [ "a", { "b": "c" }, { "d": [ 1, 2 ] } ], 94 | "second": [ "x", { "b": 1 }, { "d": [ 1.0, 3, "" ] }, null ], 95 | "patch": [ 96 | { "op": "replace", "path": "/0", "value": "x" }, 97 | { "op": "replace", "path": "/1/b", "value": 1 }, 98 | { "op": "replace", "path": "/2/d/1", "value": 3 }, 99 | { "op": "add", "path": "/2/d/-", "value": "" }, 100 | { "op": "add", "path": "/-", "value": null } 101 | ] 102 | }, 103 | { 104 | "first": { "b": "a" }, 105 | "second": { "c": "a" }, 106 | "patch": [ 107 | { "op": "move", "from": "/b", "path": "/c" } 108 | ] 109 | }, 110 | { 111 | "first": { "b": [1, 2] }, 112 | "second": { "c": [1.0, 2] }, 113 | "patch": [ 114 | { "op": "move", "from": "/b", "path": "/c" } 115 | ] 116 | }, 117 | { 118 | "first": [0, 1, 2], 119 | "second": [1, 2, 0], 120 | "patch": [ 121 | { "op": "move", "from": "/0", "path": "/-" } 122 | ] 123 | }, 124 | { 125 | "first": [0, 1, 2, 3, 4, 5], 126 | "second": [1, 3, 4, 0, 5], 127 | "patch": [ 128 | { "op": "remove", "path": "/2" }, 129 | { "op": "move", "from": "/0", "path": "/3" } 130 | ] 131 | }, 132 | { 133 | "first": [0, 1, 2, 3, 4, 5, 6, 7], 134 | "second": [3, 6, 4, 5, 7], 135 | "patch": [ 136 | { "op": "remove", "path": "/0" }, 137 | { "op": "remove", "path": "/0" }, 138 | { "op": "remove", "path": "/0" }, 139 | { "op": "move", "from": "/3", "path": "/1" } 140 | ] 141 | }, 142 | { 143 | "first": { "b": [0, 1, 2, 3], "c": [1] }, 144 | "second": { "b": [1, 3], "c": [0, 1] }, 145 | "patch": [ 146 | { "op": "remove", "path": "/b/2" }, 147 | { "op": "move", "from": "/b/0", "path": "/c/0" } 148 | ] 149 | }, 150 | { 151 | "first": { "b": [0, 1, 2, 3], "c": [1], "d": [] }, 152 | "second": { "b": [1, 3], "c": [2, 1], "d": [0] }, 153 | "patch": [ 154 | { "op": "move", "from": "/b/2", "path": "/c/0" }, 155 | { "op": "move", "from": "/b/0", "path": "/d/-" } 156 | ] 157 | }, 158 | { 159 | "first": { "b": [0, 1, 2, 3], "c": [1], "d": [] }, 160 | "second": { "b": [1, 3], "c": [0, 1], "d": [2] }, 161 | "patch": [ 162 | { "op": "move", "from": "/b/0", "path": "/c/0" }, 163 | { "op": "move", "from": "/b/1", "path": "/d/-" } 164 | ] 165 | }, 166 | { 167 | "first": { "a": 0, "b": [1, 2] }, 168 | "second": { "b": [1, 2, 0] }, 169 | "patch": [ 170 | { "op": "move", "from": "/a", "path": "/b/-" } 171 | ] 172 | }, 173 | { 174 | "first": { "b": [0, 1, 2] }, 175 | "second": { "b": [1, 2], "c": 0 }, 176 | "patch": [ 177 | { "op": "move", "from": "/b/0", "path": "/c" } 178 | ] 179 | }, 180 | { 181 | "first": { "b": [0, 1, 3, 4, 5] }, 182 | "second": { "b": [1, 2, 3, 5], "c": 0 }, 183 | "patch": [ 184 | { "op": "move", "from": "/b/0", "path": "/c" }, 185 | { "op": "add", "path": "/b/1", "value": 2 }, 186 | { "op": "remove", "path": "/b/3" } 187 | ] 188 | }, 189 | { 190 | "first": { "b": [0, 1, 3, 4, 5] }, 191 | "second": { "b": [1, 2, 3, 5], "c": 0, "d": 4 }, 192 | "patch": [ 193 | { "op": "move", "from": "/b/0", "path": "/c" }, 194 | { "op": "move", "from": "/b/2", "path": "/d" }, 195 | { "op": "add", "path": "/b/1", "value": 2 } 196 | ] 197 | }, 198 | { 199 | "first": { "b": [0, 1, 3, 4, 5] }, 200 | "second": { "b": [1, 2, 3, 5], "c": 4, "d": 0 }, 201 | "patch": [ 202 | { "op": "move", "from": "/b/3", "path": "/c" }, 203 | { "op": "move", "from": "/b/0", "path": "/d" }, 204 | { "op": "add", "path": "/b/1", "value": 2 } 205 | ] 206 | }, 207 | { 208 | "first": { "b": [0, 1, 2, 3, 4, 5, 6, 7, 8] }, 209 | "second": { "b": [1, 6, 2, 3, 5, 7, 0, 8], "c": 4 }, 210 | "patch": [ 211 | { "op": "move", "from": "/b/4", "path": "/c" }, 212 | { "op": "move", "from": "/b/5", "path": "/b/2" }, 213 | { "op": "move", "from": "/b/0", "path": "/b/6" } 214 | ] 215 | }, 216 | { 217 | "first": { "b": [0, 1, 2, 3, 4, 5, 6, 7, 8] }, 218 | "second": { "b": [1, 3, 6, 4, 5, 7, 8], "c": 2 }, 219 | "patch": [ 220 | { "op": "move", "from": "/b/2", "path": "/c" }, 221 | { "op": "remove", "path": "/b/0" }, 222 | { "op": "move", "from": "/b/4", "path": "/b/2" } 223 | ] 224 | }, 225 | { 226 | "first": { "b": [0, 1, 2, 3, 4, 5, 6, 7, 8] }, 227 | "second": { "b": [1, 3, 6, 4, 5, 7, 0, 8], "c": 2 }, 228 | "patch": [ 229 | { "op": "move", "from": "/b/2", "path": "/c" }, 230 | { "op": "move", "from": "/b/5", "path": "/b/3" }, 231 | { "op": "move", "from": "/b/0", "path": "/b/6" } 232 | ] 233 | }, 234 | { 235 | "first": { "b": [0, 1, 2, 3, 4, 5, 6, 7, 8] }, 236 | "second": { "b": [1, 3, 6, 4, 5, 7, 0, 8], "c": 2 }, 237 | "patch": [ 238 | { "op": "move", "from": "/b/2", "path": "/c" }, 239 | { "op": "move", "from": "/b/5", "path": "/b/3" }, 240 | { "op": "move", "from": "/b/0", "path": "/b/6" } 241 | ] 242 | }, 243 | { 244 | "first": {}, 245 | "second": { "a": 1, "b": 1}, 246 | "patch": [ 247 | { "op": "add", "path": "/a", "value": 1 }, 248 | { "op": "add", "path": "/b", "value": 1 } 249 | ] 250 | }, 251 | { 252 | "first": {}, 253 | "second": { "a": {}, "b": {}}, 254 | "patch": [ 255 | { "op": "add", "path": "/a", "value": {} }, 256 | { "op": "add", "path": "/b", "value": {} } 257 | ] 258 | }, 259 | { 260 | "first": {}, 261 | "second": { "a": { "a": 1 }, "b": { "a": 1.0 }}, 262 | "patch": [ 263 | { "op": "add", "path": "/a", "value": { "a": 1 } }, 264 | { "op": "copy", "from": "/a", "path": "/b" } 265 | ] 266 | }, 267 | { 268 | "first": [], 269 | "second": [ [ 0 ], [ 0 ] ], 270 | "patch": [ 271 | { "op": "add", "path": "/-", "value": [ 0 ] }, 272 | { "op": "add", "path": "/-", "value": [ 0 ] } 273 | ] 274 | }, 275 | { 276 | "first": [ "eol" ], 277 | "second": [ { "a": 1 }, { "a": 1.0 }, [], [], [ 0 ], [ 0 ], "eol" ], 278 | "patch": [ 279 | { "op": "add", "path": "/0", "value": { "a": 1 } }, 280 | { "op": "copy", "from": "/0", "path": "/1" }, 281 | { "op": "add", "path": "/2", "value": [] }, 282 | { "op": "add", "path": "/3", "value": [] }, 283 | { "op": "add", "path": "/4", "value": [ 0 ] }, 284 | { "op": "copy", "from": "/4", "path": "/5" } 285 | ] 286 | }, 287 | { 288 | "first": [ 1, 2 ], 289 | "second": [ 2, 1 ], 290 | "patch": [ { "op": "move", "from": "/1", "path": "/0" } ] 291 | }, 292 | { 293 | "first": [ { "name": "a" }, { "name": "b" }, { "name": "c" } ], 294 | "second": [ { "name": "b" } ], 295 | "patch": [ 296 | { "op": "remove", "path": "/0" }, 297 | { "op": "remove", "path": "/1" } 298 | ] 299 | }, 300 | { 301 | "first": { "b": [0, 1, 2] }, 302 | "second": { "b": [1, 2], "c": 0 ,"d":0}, 303 | "patch": [ 304 | { "op": "move", "from": "/b/0", "path": "/c"}, 305 | { "op": "add", "path": "/d", "value": 0} 306 | ] 307 | }, 308 | { 309 | "first": ["a","b","c","d","e"], 310 | "second": ["e","a","f","c","d","b"] 311 | }, 312 | { 313 | "first": [1,2,3,4,5,{"0":"0"}], 314 | "second": [1,2,4,5,{"a":"0"}] 315 | }, 316 | { 317 | "first": [0,1,2,3], 318 | "second": [1,3] 319 | } 320 | ] --------------------------------------------------------------------------------