GithubHelp home page GithubHelp logo

flipkart-incubator / zjsonpatch Goto Github PK

View Code? Open in Web Editor NEW
521.0 67.0 148.0 359 KB

This is an implementation of RFC 6902 JSON Patch written in Java

License: Apache License 2.0

Java 100.00%
java json jsonpatch jsondiff jsondiffpatch circleci json-document compaction

zjsonpatch's Introduction

CircleCI Join the chat at https://gitter.im/zjsonpatch/community

This is an implementation of RFC 6902 JSON Patch written in Java.

Description & Use-Cases

  • Java Library to find / apply JSON Patches according to RFC 6902.
  • JSON Patch defines a JSON document structure for representing changes to a JSON document.
  • It can be used to avoid sending a whole document when only a part has changed, thus reducing network bandwidth requirements if data (in JSON format) is required to send across multiple systems over network or in case of multi DC transfer.
  • When used in combination with the HTTP PATCH method as per RFC 5789 HTTP PATCH, it will do partial updates for HTTP APIs in a standard way.

Compatible with : Java 7+ versions

Code Coverage

Package Class, % Method, % Line, %
all classes 100% (6/ 6) 93.6% (44/ 47) 96.2% (332/ 345)

Complexity

  • To find JsonPatch : Ω(N+M) ,N and M represents number of keys in first and second json respectively / O(summation of la*lb) where la , lb represents JSON array of length la / lb of against same key in first and second JSON ,since LCS is used to find difference between 2 JSON arrays there of order of quadratic.
  • To Optimize Diffs ( compact move and remove into Move ) : Ω(D) / O(D*D) where D represents number of diffs obtained before compaction into Move operation.
  • To Apply Diff : O(D) where D represents number of diffs

How to use:

Current Version : 0.4.16

Add following to <dependencies/> section of your pom.xml -

<groupId>com.flipkart.zjsonpatch</groupId>
<artifactId>zjsonpatch</artifactId>
<version>{version}</version>

API Usage

Obtaining JSON Diff as patch

JsonNode patch = JsonDiff.asJson(JsonNode source, JsonNode target)

Computes and returns a JSON patch from source to target, Both source and target must be either valid JSON objects or arrays or values. Further, if resultant patch is applied to source, it will yield target.

The algorithm which computes this JsonPatch currently generates following operations as per RFC 6902 -

  • add
  • remove
  • replace
  • move
  • copy

Apply Json Patch

JsonNode target = JsonPatch.apply(JsonNode patch, JsonNode source);

Given a patch, it apply it to source JSON and return a target JSON which can be ( JSON object or array or value ). This operation performed on a clone of source JSON ( thus, the source JSON is unmodified and can be used further).

To turn off MOVE & COPY Operations

EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone()
JsonNode patch = JsonDiff.asJson(JsonNode source, JsonNode target, flags)

Example

First Json

{"a": 0,"b": [1,2]}

Second json ( the json to obtain )

 {"b": [1,2,0]}

Following patch will be returned:

[{"op":"move","from":"/a","path":"/b/2"}]

here "op" specifies the operation ("move"), "from" specifies the path from where the value should be moved, and "path" specifies where value should be moved. The value that is moved is taken as the content at the "from" path.

Apply Json Patch In-Place

JsonPatch.applyInPlace(JsonNode patch, JsonNode source);

Given a patch, it will apply it to the source JSON mutating the instance, opposed to JsonPatch.apply which returns a new instance with the patch applied, leaving the source unchanged.

Tests:

  1. 100+ selective hardcoded different input JSONs , with their driver test classes present under /test directory.
  2. Apart from selective input, a deterministic random JSON generator is present under ( TestDataGenerator.java ), and its driver test class method is JsonDiffTest.testGeneratedJsonDiff().

*** Tests can only show presence of bugs and not their absence ***

zjsonpatch's People

Contributors

akshatpriyansh avatar alexanderyastrebov avatar anatolii avatar bryant1410 avatar corneliouzbett avatar ctranxuan avatar dandoug avatar dependabot[bot] avatar elisaherold avatar fpavageau avatar gitter-badger avatar hansjoachim avatar holograph avatar hugebdu avatar isopropylcyanide avatar llper avatar luke-stead-sonocent avatar mbolgariw avatar puntogil avatar sullis avatar swaranga avatar valfirst avatar vermapratyush avatar vishwakarma avatar wojciechbulaty avatar ysangkok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zjsonpatch's Issues

The full object path is not getting removed

While generating a patch through the method JsonDiff.asJson(final JsonNode source, final JsonNode target) we run into a strange behavior. Using this method with source objects wrapped into a list and empty target objects, we get as a diff result the all keys removed but not the full path of the object.

Example:

Json source object:

{
  "id" : "XXX"
  "fields" : [ {
    "id" : "R9qG4YR0000006fdI7",
    "folder" : false
  }, {
    "id" : "R9qG6mA1Os0kyYGvFn",
    "folder" : false
  }]
}

Json target object:

{
  "id" : "XXX"
  "fields" : [
  {
  },
 {
    "id" : "R9qG6mA1Os0kyYGvFn",
    "folder" : false
  }]
}

We get:

[
  {
    "op": "remove",
    "path": "/fields/0/id",
    "value": "R9qG4YR0000006fdI7"
  },
  {
    "op": "remove",
    "path": "/fields/0/folder",
    "value": false
  }
]

We expect:

[
  {
    "op": "remove",
    "path": "/fields/0"
  }
]

Are we missing something obvious? Is this the expected behavior?

Crash with "remove" operation when path contains a large integer value.

Hi Json Patch team,

We are using zjson patch to help us manage user preferences, we use unix timestamps as a unique ID in the data we are storing. When we use a remove operation on paths that include these keys we get a crash in JsonDiff.java. Here is a sample patch operation that produces it:
{"op":"remove","path":"/profiles/AdvancedCharting/profiles/1509638193736/tradingView/data/charts/0/panes/0/sources/96/metaInfo/matchIndex"}

The problem comes from the timestamp key "1509638193736". The crash occurs at line 104 in JsonDiff.java, it seems to be trying to detect if any of the items in the path are integers using the "isInteger" function and then cast them to integers if they are. However, the timestamps appear as integers but are much larger than the largest possible integer so when the cast to int is attempted it fails and crashes.

Please let me know if there is any more information I can provide.

Patch with context (created with ADD_ORIGINAL_VALUE_ON_REPLACE) must fail on value mismatch

You have useful com.flipkart.zjsonpatch.DiffFlags.ADD_ORIGINAL_VALUE_ON_REPLACE flag on patch creation. But such context fully ignored on apply time!

Please look at test:

	def "Test create patch with context, and fail on apply when original object changed"(){
		when:
			JsonNode source = toJson '{"k1":"v1","k2":"v2"}'
			JsonNode target = toJson '{"k1":"v1","k2": {"inner": 1}}'

			JsonNode patch = asJson(source, target, EnumSet.of(ADD_ORIGINAL_VALUE_ON_REPLACE))
		then:
			patch as String == '[{"op":"replace","fromValue":"v2","path":"/k2","value":{"inner":1}}]'

		when:
			source = toJson '{"k1":"v1","k2":"v3 changed"}' // Our external changes in source object (configuration in production by customer)
			JsonNode targetRecreated = apply(patch, source)

		then:
//			JsonPatchApplicationException e = thrown(JsonPatchApplicationException) // I want exception there, but it succeed!
			targetRecreated as String == '{"k1":"v1","k2":{"inner":1}}'
	}

Now it succeed, but I want mode when it failed with JsonPatchApplicationException and message like "Can't apply patch on path [/k2] because it have unexpected value [v3 changed]. Expected value tracked in patch [v2]".

I think there should be added corresponding value for that like com.flipkart.zjsonpatch.CompatibilityFlags#CHECK_ORIGINAL_VALUE.

Are you willing I provide PR for such fix?

Type mismatch in comparison

Is it possible to add an enum for type mismatch as well?

In one of my use case, the value at a particular JSON patch was same but one was of type string and other was of type long and I wasn't able to figure out the reason for failure.

Inconsistent patch from derived diff when applied to source object

In the following code sample, there is a source and target object. The target object is derived from the source through the adjustment of some of the fields within the source. A difference is computed and then applied to the source. That resulting object is then compared to the target object with the expectation no difference should be detected. Yet, this is not the case; there is a difference in one of the fields.

package com.cyberfront.build;

import java.io.IOException;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.JsonDiff;
import com.flipkart.zjsonpatch.JsonPatch;

public class Main {
    private static final String stringSource = "{\"@type\":\"SimpleCollection\",\"id\":\"17aead29-2097-436d-b9d2-d95e0de423db\",\"notes\":\"sapien minim mandamus fugit postulant nominavi solet numquam\",\"description\":\"qui splendide porttitor simul maiestatis fabellas viverra omnesque\",\"version\":7,\"collectionValue\":[{\"@type\":\"SimpleReference\",\"id\":\"a08f2ab0-cf27-440b-b9f5-71b1021aa206\",\"notes\":\"intellegebat doctus signiferumque dis dicam appetere fringilla esse\",\"description\":\"sapientem massa legimus nunc ultricies sed eirmod\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleB\",\"id\":\"33b89e6e-3cd2-4f49-b053-b654f6f8df5f\",\"notes\":\"ignota adhuc convenire splendide vivendo\",\"description\":\"nostra efficitur morbi sit fusce tacimates eum vitae\",\"version\":0,\"intValue\":899213098,\"type\":\"SIMPLE_B\"},\"type\":\"SIMPLE_REFERENCE\"},{\"@type\":\"SimpleReference\",\"id\":\"a08f2ab0-cf27-440b-b9f5-71b1021aa206\",\"notes\":\"intellegebat doctus signiferumque dis dicam appetere fringilla esse\",\"description\":\"sapientem massa legimus nunc ultricies sed eirmod\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleB\",\"id\":\"33b89e6e-3cd2-4f49-b053-b654f6f8df5f\",\"notes\":\"ignota adhuc convenire splendide vivendo\",\"description\":\"nostra efficitur morbi sit fusce tacimates eum vitae\",\"version\":0,\"intValue\":899213098,\"type\":\"SIMPLE_B\"},\"type\":\"SIMPLE_REFERENCE\"},{\"@type\":\"SimpleD\",\"id\":\"41ef6628-6ff6-4be4-b8ab-f836f30e8f58\",\"notes\":\"adolescens mea phasellus facilisis unum\",\"description\":\"inceptos petentium etiam efficiantur wisi venenatis\",\"version\":0,\"booleanValue\":false,\"type\":\"SIMPLE_D\"},{\"@type\":\"SimpleB\",\"id\":\"12a771f8-2d9d-4060-b02c-2772862154ff\",\"notes\":\"hendrerit civibus sagittis congue inceptos ante facilis honestatis\",\"description\":\"antiopam reprimique putent urbanitas ne volumus\",\"version\":2,\"intValue\":-1035459272,\"type\":\"SIMPLE_B\"}],\"type\":\"SIMPLE_COLLECTION\"}";
    private static final String stringTarget = "{\"@type\":\"SimpleCollection\",\"id\":\"17aead29-2097-436d-b9d2-d95e0de423db\",\"notes\":\"sapien minim mandamus fugit postulant nominavi solet numquam\",\"description\":\"qui splendide porttitor simul maiestatis fabellas viverra omnesque\",\"version\":10,\"collectionValue\":[{\"@type\":\"SimpleReference\",\"id\":\"f4b10497-ecb9-4e6a-aa66-bd4c874da6f0\",\"notes\":\"cras habitant liber verterem neque litora eruditi vehicula\",\"description\":\"te comprehensam mutat latine deterruisset quis sadipscing non verear\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleD\",\"id\":\"d4754e2d-edfc-4263-add2-4a17082f6b50\",\"notes\":\"ceteros condimentum rhoncus mei salutatus volutpat delectus tation mollis\",\"description\":\"ante ea errem mnesarchum civibus\",\"version\":0,\"booleanValue\":true,\"type\":\"SIMPLE_D\"},\"type\":\"SIMPLE_REFERENCE\"},{\"@type\":\"SimpleReference\",\"id\":\"a08f2ab0-cf27-440b-b9f5-71b1021aa206\",\"notes\":\"intellegebat doctus signiferumque dis dicam appetere fringilla esse\",\"description\":\"sapientem massa legimus nunc ultricies sed eirmod\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleB\",\"id\":\"33b89e6e-3cd2-4f49-b053-b654f6f8df5f\",\"notes\":\"ignota adhuc convenire splendide vivendo\",\"description\":\"nostra efficitur morbi sit fusce tacimates eum vitae\",\"version\":0,\"intValue\":899213098,\"type\":\"SIMPLE_B\"},\"type\":\"SIMPLE_REFERENCE\"},{\"@type\":\"SimpleReference\",\"id\":\"a08f2ab0-cf27-440b-b9f5-71b1021aa206\",\"notes\":\"intellegebat doctus signiferumque dis dicam appetere fringilla esse\",\"description\":\"sapientem massa legimus nunc ultricies sed eirmod\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleB\",\"id\":\"33b89e6e-3cd2-4f49-b053-b654f6f8df5f\",\"notes\":\"ignota adhuc convenire splendide vivendo\",\"description\":\"nostra efficitur morbi sit fusce tacimates eum vitae\",\"version\":0,\"intValue\":899213098,\"type\":\"SIMPLE_B\"},\"type\":\"SIMPLE_REFERENCE\"},{\"@type\":\"SimpleB\",\"id\":\"12a771f8-2d9d-4060-b02c-2772862154ff\",\"notes\":\"morbi fermentum inani tritani malorum ultrices\",\"description\":\"antiopam reprimique putent urbanitas ne volumus\",\"version\":3,\"intValue\":-1035459272,\"type\":\"SIMPLE_B\"}],\"type\":\"SIMPLE_COLLECTION\"}";

    private final static ObjectMapper mapper = new ObjectMapper();

	public static void main(String[] args) {
    	try {
            // Convert the stringSource into a JSON object
        	JsonNode jsonSource = mapper.readTree(stringSource);

            // Convert the stringTarget into a JSON object
            JsonNode jsonTarget = mapper.readTree(stringTarget);

	        // Compute the patch required to transform jsonSource into jsonTarget
    	    JsonNode jsonSourceTargetDiff = JsonDiff.asJson(jsonSource, jsonTarget);

        	// Apply that patch back to jsonSource.  The result should identical to jsonTarget
            JsonNode jsonSourceAppliedDiff = JsonPatch.apply(jsonSourceTargetDiff, jsonSource);

	        // Identify the difference between the target object and the JSON object we just computed by applying the difference to the
    	    // source.  This should result in an empty array since applying the patch should result in the target, but it isn't empty.
            JsonNode jsonTargetDiffDiff = JsonDiff.asJson(jsonSourceAppliedDiff, jsonTarget);
        
            System.out.println("           jsonSource: " + jsonSource.toString());
            System.out.println("           jsonTarget: " + jsonTarget.toString());
            System.out.println(" jsonSourceTargetDiff: " + jsonSourceTargetDiff.toString());
            System.out.println("jsonSourceAppliedDiff: " + jsonSourceAppliedDiff.toString());
            System.out.println("   jsonTargetDiffDiff: " + jsonTargetDiffDiff.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This produces the following output:

           jsonSource: {"@type":"SimpleCollection","id":"17aead29-2097-436d-b9d2-d95e0de423db","notes":"sapien minim mandamus fugit postulant nominavi solet numquam","description":"qui splendide porttitor simul maiestatis fabellas viverra omnesque","version":7,"collectionValue":[{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleD","id":"41ef6628-6ff6-4be4-b8ab-f836f30e8f58","notes":"adolescens mea phasellus facilisis unum","description":"inceptos petentium etiam efficiantur wisi venenatis","version":0,"booleanValue":false,"type":"SIMPLE_D"},{"@type":"SimpleB","id":"12a771f8-2d9d-4060-b02c-2772862154ff","notes":"hendrerit civibus sagittis congue inceptos ante facilis honestatis","description":"antiopam reprimique putent urbanitas ne volumus","version":2,"intValue":-1035459272,"type":"SIMPLE_B"}],"type":"SIMPLE_COLLECTION"}
           jsonTarget: {"@type":"SimpleCollection","id":"17aead29-2097-436d-b9d2-d95e0de423db","notes":"sapien minim mandamus fugit postulant nominavi solet numquam","description":"qui splendide porttitor simul maiestatis fabellas viverra omnesque","version":10,"collectionValue":[{"@type":"SimpleReference","id":"f4b10497-ecb9-4e6a-aa66-bd4c874da6f0","notes":"cras habitant liber verterem neque litora eruditi vehicula","description":"te comprehensam mutat latine deterruisset quis sadipscing non verear","version":0,"referenceValue":{"@type":"SimpleD","id":"d4754e2d-edfc-4263-add2-4a17082f6b50","notes":"ceteros condimentum rhoncus mei salutatus volutpat delectus tation mollis","description":"ante ea errem mnesarchum civibus","version":0,"booleanValue":true,"type":"SIMPLE_D"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleB","id":"12a771f8-2d9d-4060-b02c-2772862154ff","notes":"morbi fermentum inani tritani malorum ultrices","description":"antiopam reprimique putent urbanitas ne volumus","version":3,"intValue":-1035459272,"type":"SIMPLE_B"}],"type":"SIMPLE_COLLECTION"}
 jsonSourceTargetDiff: [{"op":"replace","path":"/version","value":10},{"op":"add","path":"/collectionValue/0","value":{"@type":"SimpleReference","id":"f4b10497-ecb9-4e6a-aa66-bd4c874da6f0","notes":"cras habitant liber verterem neque litora eruditi vehicula","description":"te comprehensam mutat latine deterruisset quis sadipscing non verear","version":0,"referenceValue":{"@type":"SimpleD","id":"d4754e2d-edfc-4263-add2-4a17082f6b50","notes":"ceteros condimentum rhoncus mei salutatus volutpat delectus tation mollis","description":"ante ea errem mnesarchum civibus","version":0,"booleanValue":true,"type":"SIMPLE_D"},"type":"SIMPLE_REFERENCE"}},{"op":"replace","path":"/collectionValue/3/@type","value":"SimpleB"},{"op":"replace","path":"/collectionValue/3/id","value":"12a771f8-2d9d-4060-b02c-2772862154ff"},{"op":"replace","path":"/collectionValue/3/notes","value":"morbi fermentum inani tritani malorum ultrices"},{"op":"replace","path":"/collectionValue/3/description","value":"antiopam reprimique putent urbanitas ne volumus"},{"op":"replace","path":"/collectionValue/3/version","value":3},{"op":"remove","path":"/collectionValue/3/booleanValue","value":false},{"op":"replace","path":"/collectionValue/3/type","value":"SIMPLE_B"},{"op":"copy","from":"/collectionValue/3/intValue","path":"/collectionValue/3/intValue"},{"op":"remove","path":"/collectionValue/4","value":{"@type":"SimpleB","id":"12a771f8-2d9d-4060-b02c-2772862154ff","notes":"hendrerit civibus sagittis congue inceptos ante facilis honestatis","description":"antiopam reprimique putent urbanitas ne volumus","version":2,"intValue":-1035459272,"type":"SIMPLE_B"}}]
jsonSourceAppliedDiff: {"@type":"SimpleCollection","id":"17aead29-2097-436d-b9d2-d95e0de423db","notes":"sapien minim mandamus fugit postulant nominavi solet numquam","description":"qui splendide porttitor simul maiestatis fabellas viverra omnesque","version":10,"collectionValue":[{"@type":"SimpleReference","id":"f4b10497-ecb9-4e6a-aa66-bd4c874da6f0","notes":"cras habitant liber verterem neque litora eruditi vehicula","description":"te comprehensam mutat latine deterruisset quis sadipscing non verear","version":0,"referenceValue":{"@type":"SimpleD","id":"d4754e2d-edfc-4263-add2-4a17082f6b50","notes":"ceteros condimentum rhoncus mei salutatus volutpat delectus tation mollis","description":"ante ea errem mnesarchum civibus","version":0,"booleanValue":true,"type":"SIMPLE_D"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleReference","id":"a08f2ab0-cf27-440b-b9f5-71b1021aa206","notes":"intellegebat doctus signiferumque dis dicam appetere fringilla esse","description":"sapientem massa legimus nunc ultricies sed eirmod","version":0,"referenceValue":{"@type":"SimpleB","id":"33b89e6e-3cd2-4f49-b053-b654f6f8df5f","notes":"ignota adhuc convenire splendide vivendo","description":"nostra efficitur morbi sit fusce tacimates eum vitae","version":0,"intValue":899213098,"type":"SIMPLE_B"},"type":"SIMPLE_REFERENCE"},{"@type":"SimpleB","id":"12a771f8-2d9d-4060-b02c-2772862154ff","notes":"morbi fermentum inani tritani malorum ultrices","description":"antiopam reprimique putent urbanitas ne volumus","version":3,"type":"SIMPLE_B","intValue":null}],"type":"SIMPLE_COLLECTION"}
   jsonTargetDiffDiff: [{"op":"replace","path":"/collectionValue/3/intValue","value":-1035459272}]

Note the value of jsonTargetDiffDiff should be an empty array, but it is not.

Below is the pom.xml for my project.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cyberfront</groupId>
    <artifactId>bad_build</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.flipkart.zjsonpatch</groupId>
            <artifactId>zjsonpatch</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.flipkart.zjsonpatch</groupId>
                <artifactId>zjsonpatch</artifactId>
                <version>0.3.1</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

I have attached the complete project below. It was produced using Eclipse neon.3.

ADD

Hello , I was expected "op":"add" for the following comparison.

target : {"title":null,"firstName":"William","middleName":null,"lastName":null,"gender":"Male"}
source : {"title":null,"firstName":null,"middleName":null,"lastName":null,"gender":null}
Result : [{"op":"replace","path":"/firstName","value":"William"}, "op":"replace","path":"/gender","value":"Male"}]

Code : JsonNode resultJson = JsonDiff.asJson(
source,target);

Version : 0.4.5

Is it expected response or am I missing anything?

Thanks
Shashank

Internal refactor of JsonDiff and add some tests.

While I was reading this I had a brief insight. With a custom tailored diff algorithm, maybe we could fix both #18 and #20.

I propose the following refactors on JsonDiff:

  1. Create better tests for JsonDiff to avoid things like #61.
  2. Use a explicit stack (ArrayDeque) for it's recursive functions, to avoid StackOverflow exceptions.
  3. Maybe refactor JsonDiff to have configurable instances instead of being only a "util"/"static" class, having one internal "default" instance.

More on 3:
Refactor JsonDiff to have the following schema:

public class JsonDiff {

    /* current static methods */

    /**
     *  separate the "compactDiffs" and "introduceCopyOperation" to their own classes.
     *  These compactor's would be executed sequentially.
     */
    private List<Compactor> compactors = new ArrayList<>();
    
    private JsonEquivalence equivalence;
    
    private EnumSet<DiffFlags> flags;

    /* diff logic */

    /* fluent setter for flags. */
}

/**
 * Maybe use a enum that implement this interface, so we can have
 * safe "singletons" for the default Compactors ("move", "copy" and "replace")
 */
interface Compactor {
    /**
     *  maybe other methods that don't require a list as param,
     * so we don't need to create unnecessary objects.
     */
    Diff compact(List<Diff> diffs);
    
}

/**
 * use custom "equivalences" for future features and solving current issue.
 */
interface JsonEquivalence{
    boolean equals(JsonNode first, JsonNode second);
}

Also, use Linear Space Myers and/or Hirschberg LCS algorithms as a base to improve perfomance and memory usage.

Of course, that would take a fair amount of time, so I am opening this issue for informative porpouses and to collect some opnions before I dive into it.

Edit: Just for clarification, most of these changes would be internal api. Maybe in future we could release it as public

Testing for a pointer that references a nonexistent value

I would like to have a "test" operation that asserts a value is missing from an object. Would this be the correct way to do so?

[
  {
    "op": "test",
    "path": "nonexistentField",
    "value": null
  }
]

Currently, this library will throw an exception "noSuchPath in source". Does it make sense to add this feature to the library?

Value in remove operation

Hello :)

Is there any reason why remove operation contains value field? E.g.

{"op":"remove","path":"/age","value": "123"}

According to the RFC, the value should not be there.

Option for marking a variable as "Not Patchable"

First of all - Amazing project.
Works really fast and it is easy to use.

I am missing just one functionality.

It would be really good (since it uses Jackson beneath) to add @Anotation for marking variables as "Not patchable".

So if patch attempt is made on variable marked with (for ex.) @DisablePatch - the patch function will throw an exception.

Is such thing possible ?

Thank you in advance.

NullPointerException thrown when some other behavior would be preferable

The following code produces a NullPointerException on line 42 of ApplyProcessor.java:

package com.cyberfront.bad_update;

import java.io.IOException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.JsonPatch;

public class Main {
    private final static String opString = "[{\"op\":\"replace\",\"path\":\"/version\",\"value\":43},{\"op\":\"move\",\"path\":\"/collectionValue/0\",\"from\":\"/collectionValue/1\"},{\"op\":\"add\",\"path\":\"/collectionValue/1\",\"value\":{\"@type\":\"SimpleReference\",\"id\":\"9c144d02-a859-4a9c-be9f-0de6fc77d5ac\",\"notes\":\"voluptatum moderatius dolorum finibus solet vituperata adhuc\",\"description\":\"falli intellegat antiopam mus antiopam\",\"version\":0,\"referenceValue\":{\"@type\":\"SimpleD\",\"id\":\"c75d0410-9729-4688-94fc-d5416a2f716e\",\"notes\":\"condimentum solum simul postea noluisse altera quam eruditi\",\"description\":\"sem egestas senserit curabitur autem\",\"version\":0,\"booleanValue\":true,\"type\":\"SIMPLE_D\"},\"type\":\"SIMPLE_REFERENCE\"}},{\"op\":\"add\",\"path\":\"/collectionValue/4\",\"value\":{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":1,\"intValue\":588448685,\"type\":\"SIMPLE_B\"}},{\"op\":\"replace\",\"path\":\"/collectionValue/9/@type\",\"value\":\"SimpleReference\"},{\"op\":\"replace\",\"path\":\"/collectionValue/9/id\",\"value\":\"82141cc1-d655-4905-ba1a-a1a4d9070466\"},{\"op\":\"replace\",\"path\":\"/collectionValue/9/notes\",\"value\":\"interdum nonumes orci vero aptent suavitate\"},{\"op\":\"replace\",\"path\":\"/collectionValue/9/description\",\"value\":\"simul aenean morbi fames tempus veri reformidans\"},{\"op\":\"remove\",\"path\":\"/collectionValue/9/intValue\"},{\"op\":\"replace\",\"path\":\"/collectionValue/9/type\",\"value\":\"SIMPLE_REFERENCE\"},{\"op\":\"add\",\"path\":\"/collectionValue/9/referenceValue\",\"value\":{\"@type\":\"SimpleB\",\"id\":\"39958676-bfa2-4963-8c01-7a0e656fd805\",\"notes\":\"numquam eloquentiam sagittis esse habemus offendit hac lectus sumo\",\"description\":\"feugait interdum expetendis dicam vituperata quem nonumes\",\"version\":0,\"intValue\":1215734120,\"type\":\"SIMPLE_B\"}},{\"op\":\"replace\",\"path\":\"/collectionValue/10/@type\",\"value\":\"SimpleA\"},{\"op\":\"replace\",\"path\":\"/collectionValue/10/id\",\"value\":\"12a3eea0-8636-4735-a4cb-03309eb14dad\"},{\"op\":\"replace\",\"path\":\"/collectionValue/10/notes\",\"value\":\"altera auctor civibus per tempor nonumes quaestio cetero\"},{\"op\":\"replace\",\"path\":\"/collectionValue/10/description\",\"value\":\"gravida sapientem saepe voluptatum aptent pri\"},{\"op\":\"remove\",\"path\":\"/collectionValue/10/intValue\"},{\"op\":\"replace\",\"path\":\"/collectionValue/10/type\",\"value\":\"SIMPLE_A\"},{\"op\":\"move\",\"path\":\"/collectionValue/10/stringValue\",\"from\":\"/collectionValue/11/stringValue\"},{\"op\":\"add\",\"path\":\"/collectionValue/12\",\"value\":{\"@type\":\"SimpleD\",\"id\":\"95a81a99-ce21-40c7-a152-8db57ea34a76\",\"notes\":\"ancillae quisque eget ius praesent omittantur senserit nunc finibus\",\"description\":\"ridens dolorum veritus neque hinc menandri netus\",\"version\":0,\"booleanValue\":false,\"type\":\"SIMPLE_D\"}}]";
    private final static String docString = "{\"@type\":\"SimpleCollection\",\"id\":\"6b9c1dea-566c-4284-aab6-c590658fa02e\",\"notes\":\"fabulas litora labores curabitur vestibulum finibus vehicula risus venenatis\",\"description\":\"pertinacia conubia mi noster electram curabitur dicant dignissim\",\"version\":30,\"collectionValue\":[{\"@type\":\"SimpleA\",\"id\":\"12a3eea0-8636-4735-a4cb-03309eb14dad\",\"notes\":\"altera auctor civibus per tempor nonumes quaestio cetero\",\"description\":\"gravida sapientem saepe voluptatum aptent pri\",\"version\":0,\"stringValue\":\"airline-fan-dream-just\",\"type\":\"SIMPLE_A\"},{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":0,\"intValue\":273482951,\"type\":\"SIMPLE_B\"},{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":0,\"intValue\":273482951,\"type\":\"SIMPLE_B\"},{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":0,\"intValue\":273482951,\"type\":\"SIMPLE_B\"},{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":0,\"intValue\":273482951,\"type\":\"SIMPLE_B\"},{\"@type\":\"SimpleC\",\"id\":\"e18f7bec-b247-4058-9a6c-c6b52af26388\",\"notes\":\"option veri pellentesque ultricies adipiscing urbanitas disputationi sed consectetur\",\"description\":\"pertinacia dicam odio ante eu utroque fames altera natoque\",\"version\":0,\"doubleValue\":0.07255739886557588,\"type\":\"SIMPLE_C\"},{\"@type\":\"SimpleB\",\"id\":\"e1011e30-9bd5-470e-a40a-4c3d7b621e16\",\"notes\":\"liber vix elitr interpretaris aliquip dico quidam\",\"description\":\"aenean adversarium vivamus id porta curabitur placerat invidunt\",\"version\":0,\"intValue\":273482951,\"type\":\"SIMPLE_B\"},{\"@type\":\"SimpleC\",\"id\":\"d4cfc023-1ad3-4af2-9d45-969eb1e18497\",\"notes\":\"utroque ocurreret latine mediocritatem aenean porta recteque tristique\",\"description\":\"quam fabulas molestiae pertinax elaboraret mei tempus veritus hinc\",\"version\":0,\"doubleValue\":0.16580470519942858,\"type\":\"SIMPLE_C\"},{\"@type\":\"SimpleC\",\"id\":\"d4cfc023-1ad3-4af2-9d45-969eb1e18497\",\"notes\":\"utroque ocurreret latine mediocritatem aenean porta recteque tristique\",\"description\":\"quam fabulas molestiae pertinax elaboraret mei tempus veritus hinc\",\"version\":0,\"doubleValue\":0.16580470519942858,\"type\":\"SIMPLE_C\"}],\"type\":\"SIMPLE_COLLECTION\"}";

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode op = null;
        JsonNode doc = null;
        JsonNode rv = null;

        try {
            op = mapper.readTree(opString);
            doc = mapper.readTree(docString);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (null != op && null != doc) {
            rv = JsonPatch.apply(op, doc);
        }

        System.out.println(rv.toString());
    }
}

The actual exception generated is:

Exception in thread "main" java.lang.NullPointerException
    at com.flipkart.zjsonpatch.ApplyProcessor.move(ApplyProcessor.java:42)
    at com.flipkart.zjsonpatch.JsonPatch.process(JsonPatch.java:102)
    at com.flipkart.zjsonpatch.JsonPatch.apply(JsonPatch.java:135)
    at com.flipkart.zjsonpatch.JsonPatch.apply(JsonPatch.java:140)
    at com.cyberfront.bad_update.Main.main(Main.java:27)

My pom.xml file is:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cyberfront</groupId>
    <artifactId>bad_update</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.flipkart.zjsonpatch</groupId>
            <artifactId>zjsonpatch</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.flipkart.zjsonpatch</groupId>
                <artifactId>zjsonpatch</artifactId>
                <version>0.3.1</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Assuming the operation is actually invalid on the given JSON object (which I strongly suspect to be the case, though haven't investigated thoroughly to verify), I would think a more descriptive exception, probably derived from JsonPatchApplicationException would be in order here, rather than a NullPointerException.

Let me know if you need other examples where this happens; I can generate them more or less on demand or as needed.

Complete zipped Eclipse Neon project is attached.

bad_update.zip

complex json array,the result of comparison is inaccurate.

eg.
source:[6772982,20190118,[19]]
target:[6772982]

EnumSet flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone();
JsonNode diffResultNode = JsonDiff.asJson(actualNode, expectNode, flags);
System.out.println(diffResultNode);

result:
[{"op":"remove","path":"/1","value":20190118},{"op":"remove","path":"/1","value":[19]}]

i think, the result should be:
[{"op":"remove","path":"/1","value":20190118},{"op":"remove","path":"/2","value":[19]}]

Allow for fuzzy patching

In some applications it is useful to support "fuzzy" patching where it is acceptable for some patch operations to fail. Ideally, such an operation would be able to report all failed patch operations.

Change pom to latest 2.8.10 jackson release

Hello,
since jackson 2.9 has been released, I would also build a zjsonpatch release targeting jackson 2.8.10 (latest 2.8). This will increase compliance with all software using latest 2.8.10 version.

KR,
D

Json Diff

while doing the json node diff in replace operation it should print new value as well.
in current scenario it is printing only old value.

Memory issue with 0.4.0+

It seems that In newer versions (0.4.0+) JsonDiff consumes significantly more memory. Most probably this is related to #60 and getLCS method.

For version 0.4.4:

An exception or error caused a run to abort: Java heap space 
java.lang.OutOfMemoryError: Java heap space
	at com.flipkart.zjsonpatch.InternalUtils.longestCommonSubsequence(InternalUtils.java:31)
	at com.flipkart.zjsonpatch.JsonDiff.getLCS(JsonDiff.java:446)
	at com.flipkart.zjsonpatch.JsonDiff.compareArray(JsonDiff.java:337)
	at com.flipkart.zjsonpatch.JsonDiff.generateDiffs(JsonDiff.java:324)
	at com.flipkart.zjsonpatch.JsonDiff.compareObjects(JsonDiff.java:425)
	at com.flipkart.zjsonpatch.JsonDiff.generateDiffs(JsonDiff.java:327)
	at com.flipkart.zjsonpatch.JsonDiff.asJson(JsonDiff.java:47)
	at com.flipkart.zjsonpatch.JsonDiff.asJson(JsonDiff.java:38)

I'm comparing two quite large jsons, around 700 Kb each, with around 300Mb of memory.
It is caused by comparing two arrays with around 40 000 of strings each.

Version 0.3.10 works just fine.

Interest to remove/migrate apache and guava dependencies

while reading the source code, I realized that most of the methods used from apache and guava libraries could be ported to the kotlin std lib or written by hand. I think that the library would benefit much from the shrink in dependencies, since guava is almost 2.5MB

So I ask if there is interest in migrating? I can make a commit for this after the holidays. My only worries are about java 6 compability (in kotlin std lib case) and perfomance.

The main difficult of the port would be the apache LCS algorithm that JsonDiff uses.

feature request: add a flag to provide non-cumulative diff operations instead of ONLY patch ops

USE CASE: Build graphical editor for modifying custom json schema structure and text that includes a button to identify differences graphically for user to review. Differences should be displayed inline in the editor (think tooltip/annotations inside document)
We are trying to use Zjsonpatch as the diff engine to compare the original json doc to the edited json doc which works fairly well but the cumulative nature (requiring the previous op in the array to be applied before the current path resolves correctly) of the patch operations makes it impossible to match operations to nodes without actually munging the doc, especially as the complexity of the edits compound.

I would like to use zjsonpatch to build a diff UI on my custom json schema. IF jsonpatch could be made to provide paths that do not require any to be applied, then we would be able to build any UI on top of our schema and then "annotate" the document with the patch operations and allow the users to review them without knowing anything about the underlying technologies or schemas.
json obj 1:
{ "dateRevised": null, "children": [ { "type": "TITLEPART", "id": null, "sortIndex": 0, "children": [ { "type": "TEXT", "id": null, "sortIndex": 0, "children": [ { "children": [], "content": "This" }, { "children": [], "content": " is" }, { "children": [], "content": " a" }, { "children": [], "content": " test" } ], "scheme": null, "scriptOrientation": null, "bold": null, "italic": null, "underline": null } ] } ] }

Json obj 2:
{ "dateRevised": null, "children": [ { "type": "TITLEPART", "id": null, "sortIndex": 0, "children": [ { "type": "TEXT", "id": null, "sortIndex": 0, "children": [ { "children": [], "content": "is" }, { "children": [], "content": " test" } ], "scheme": null, "scriptOrientation": null, "bold": null, "italic": null, "underline": null } ] } ] }
Current Patch:
[{"op":"replace","path":"/children/0/children/0/children/0/content","value":"is"},{"op":"remove","path":"/children/0/children/0/children/1"},{"op":"remove","path":"/children/0/children/0/children/1"}]

What I would like to have is
[ {"op":"remove","path":"/children/0/children/0/children/0"},{"op":"replace","path":"/children/0/children/0/children/1/content","value":"is"}, {"op":"remove","path":"/children/0/children/0/children/2"}, ]

I am not an RFC 6902 expert but I'm thinking that simply altering the paths to identify the source node path without respect to other operations would cover 99% of my problems. Am I off base? does anyone else have this use case? how did you solve it?

New flags introduces non compatible changes to JsonDiff.asJson

Hi this is related to Issue #51. As of Issue #58 a new optional feature was introduced adding a new flag: OMIT_ORIGINAL_VALUE_ON_REPLACE. This made the API break for public static JsonNode asJson(final JsonNode source, final JsonNode target, EnumSet<DiffFlags> flags) since I get different output with unchanged input. I think this method should be treated with the same vigilance as the method without flags. Now I must go through all changes to see if the logic has changed between versions and add corresponding flags to the call to the method.

I propose that flags in the future are added with relation to the default behaviour of the library. For #58 it should have been ADD_ORIGINAL_VALUE_ON_REPLACE instead of OMIT_. I think it is much easier to understand. In this way both public methods can be kept compatible when new flags are added.

I hope you can see the benefit of this change of principles?

Print diff on Console

Is there a way I can print the diff of two jsonFiles on console instead of just printing the patch.
I am looking for something similar to the output generated by http://www.jsondiff.com/

Does this library has this feature? Is there a way I can use this library to print changes in console.

RFC6901 compliance may present backward incompatible changes

I was working on as PR for #82. I found the this lib lacks a test for RFC6901 (JsonPointer specification), I should have noted this on #60, for which I am sorry.

I have read thru the RFC6091 spec. The doc presents a very simple Json for tests at section 5:

   {
      "foo": ["bar", "baz"],
      "": 0,
      "a/b": 1,
      "c%d": 2,
      "e^f": 3,
      "g|h": 4,
      "i\\j": 5,
      "k\"l": 6,
      " ": 7,
      "m~n": 8
   }

I have added a test to our lib:

JsonNode testData = TestUtils.loadResourceAsJsonNode("rfc6901.json");
JsonNode emptyJson = TestUtils.DEFAULT_MAPPER.createObjectNode();

JsonNode patch = JsonDiff.asJson(emptyJson, testData);
JsonNode result = JsonPatch.apply(patch, emptyJson);
assertEquals(testData, result);

Which produces the following output (formatted for clarity):

Expected:                Actual:
{                        {
   "foo":[                   "foo":[
      "bar",                    "bar",
      "baz"                     "baz"
   ],                        ],
   "":0,                     "":0,
   "a/b":1,                  "a/b":1,
   "c%d":2,                  "c%d":2,
   "e^f":3,                  "e^f":3,
   "g|h":4,                  "g|h":4,
   "i\\j":5,                 "i\\\\j":5,
   "k\"l":6,                 "k\\l":6,
   " ":7,                    " ":7,
   "m~n":8                   "m~n":8
}                         }

The test ì fails, and is related to #82.
The test k also fails, and is related to #59 (comment)

Fixing this issue would break backwards compatibility, so I would like some feedback before working on a fix.

Option to turn off copy and move

The operations move and copy implies a relation between "from" and "path". This is based on matching values but that might not be a true relation.

Consider these examples:

First: {"age": 10}
Second: {"height": 10}
Patch result: [{"op":"move","from":"/age","path":"/height"}]

or

Second: {"age": 10, "height": 10}
Patch result: [{"op":"copy","from":"/age","path":"/height"}]

"age" does not have any relation with "height", they just happen to have the same value.

Not all systems apply JSON patches to existing JSON structures. We have use cases for subscribing on changes based on the "path". Also use cases on acting differently (or not at all) based on what "path" is changed. This would be a lot simpler if only add/remove/replace are used.

My proposal is to add a flag omitting copy/move. Would you consider this as a valid addition to this library?

Supporting in-place patch

@vishwakarma Gopi, hey.

We have in our codebase (event sourcing) a chain of patches we apply one after another and the target JSONs are pretty big.

WDYT about supporting another flavour (ie JsonPatch.applyInPlace) without the deepCopy()

I can prepare a PR.

Thanks in advance!

Daniel

Support "/-" for lists

I am wondering if "-" is supported when diffing a list? Our backend uses this to distinguish between existing and new items in a collection. In RFC 6902 section 4.1 it says:

If the "-" character is used to index the end of the array (see [RFC6901]), this has the effect of appending the value to the array.

Is this supported?

Add possibility to treat arrays as values when computing json diff

I was wondering whether you would be willing to accept an extension to the library which could treat the json arrays as values when producing the diff?

This would mean adding the possibility to report the change:
{"array":["A","B","C"]} -> {"array":["C","A","B"]}
as
[{"op":"replace", "path":"/array", "value":["C","A","B"]}]

The idea is to have the possibility to know that an array has changed without knowing every little change.

Automatic Module Name

Hi. Thanks for an excellent library. I'm currently looking at migration from Java 8 to Java 11 in projects at work and am having issues relating to the module path, especially with the lack of automatic module name for this project. I understand the value of remaining compatible with Java 6, so would suggest that you add an Automatic-Module-Name entry to your manifest-file to make life easier for people using the module path as well as "reserving" a name for future reference.

I'm not well versed in Maven, but I think something like the below would work:

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Automatic-Module-Name>com.flipkart.zjsonpatch</Automatic-Module-Name>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Cheers!

bug in replace array in json

hi, thanks for this great library.
when I'm getting different between this two jsons

   "{\"b\": [1,2,4,9]}"

and

  "{\"b\": [6,2,4,9]}"

the difference value currectly return operation replace in location 0.

but if I get diff this two jsons

 "{\"b\": [2,2,5,6]}"

and

  "{\"b\": [4,2,5,6]}"

it returns add and remove operation in 0 and 2 positions.
I think the reason of this bug is because of two number 2 .

Encoding/Decoding of paths with escaping is broken

Consider the path fragment abc~0~1. When the ~ characters are escaped, this is encoded correctly as abc~00~01. However, decoding it will incorrectly yield abc~0/ .

The problem piece of code is in PathUtils.java:

class PathUtils {
    private static final Pattern ENCODED_TILDA_PATTERN = Pattern.compile("~");
    private static final Pattern ENCODED_SLASH_PATTERN = Pattern.compile("/");

    private static final Pattern DECODED_TILDA_PATTERN = Pattern.compile("~0");
    private static final Pattern DECODED_SLASH_PATTERN = Pattern.compile("~1");

    private static String encodePath(Object object) {
        String path = object.toString(); // see http://tools.ietf.org/html/rfc6901#section-4
        path = ENCODED_TILDA_PATTERN.matcher(path).replaceAll("~0");
        return ENCODED_SLASH_PATTERN.matcher(path).replaceAll("~1");
    }

    private static String decodePath(Object object) {
        String path = object.toString(); // see http://tools.ietf.org/html/rfc6901#section-4
        path = DECODED_TILDA_PATTERN.matcher(path).replaceAll("~");
        return DECODED_SLASH_PATTERN.matcher(path).replaceAll("/");
    }

decodePath needs to decode in the reverse order of encode (slash, then tilda, instead of the current order).

Non intuitive behaviour of flags

If I use asJson(final JsonNode source, final JsonNode target) I get the default behaviour which is to omit the value in remove.
If I use asJson(final JsonNode source, final JsonNode target, EnumSet<DiffFlags> flags) with no flags I do not get the default behaviour. This is non intuitive. This also means that if I want to use another flag on top of the default behaviour I need to add the OMIT_VALUE_ON_REMOVE flag as well.

I propose:
*OMIT_VALUE_ON_REMOVE is replaced by ADD_VALUE_ON_REMOVE.
*DiffFlags.defaults() is an empty set.
This way the library will be more intuitive to use.

Replace of non existing path results in addition of that path

Thanks for a great library!

I found this issue which is possible to reproduce in version 0.4.2

Given I have source json:
{ }
When I apply patch:
[
{
"op" : "replace",
"path" : "some-not-existing-path",
"value" : "some-value"
}
]
Then I get result:
{
"some-not-existing-path" : "some-value"
}

I would expect that implementation will throw an exception or will skip replacement of something which does not exist.

According to the RFC-6902 specification (https://tools.ietf.org/html/rfc6902#section-4.3):
The Replace operation functionally is identical to a "remove" operation for a value, followed immediately by an "add" operation at the same location with the replacement value.
For the Remove operation target location MUST exist for the operation to be successful.

Supporting wildcard in path

Hello,
I was wondering if its possible to add support for wildcard in path.

{
"biscuits": [
{ "name": "Digestive" },
{ "name": "Choco Leibniz" }
]
}

In the example of:
{ "op": "replace", "path": "/biscuits/*/name", "value": { "name": "ChangeEverything" } }

would lead to:

{
"biscuits": [
{ "name": "ChangeEverything" },
{ "name": "ChangeEverything" }
]
}

In case we aren't sure the amount of entries in an array, supporting this could help generalize it and allow updates everywhere. JsonPath library I believe supports it, but not sure if its practical to use it here. I will take a stab at it myself, but first wanted to get your thoughts if this has been something you have heard of before or tried.
Thanks

Syntax

Would it be possible to follow the syntax referenced in the RFC such as op for operation (instead of o) and value (instead of v) etc? Thanks.

JsonDiff.asJson 'replace' ops are reversed.

I think there's an issue with the output of JsonDiff.asJson.

While the add and remove ops are as expected, the replace op seems to have the source value, rather than the target value.

A simple workaround to this is reverse the arguments, and reverse the meaning of add and remove.

How do I access the source and target values in a reported diff between 2 jsons?

I'm trying to find the diff between two JSON objects using zjsonpatch. However, I want the results to return the source as well as the target values of the changed object. For example, the following:

ObjectMapper mapper = new ObjectMapper();

JsonNode source = null;
JsonNode revised = null;
try {
    source = mapper.readTree("{\"a\":1, \"b\":2}");
    revised = mapper.readTree("{\"a\":1, \"b\":3}");
} catch (IOException e) {
    e.printStackTrace();
}
JsonNode patch = JsonDiff.asJson(source, revised);

will return

[{"op":"replace","path":"/b","value":3}]

This is essentially how it changed, where it changed, and what the new value is. I want it to include the original value from source (in this case, it would be 2). Is there a way of me accessing this value? The data set has the potential of being very large.

Strange NumberFormatException when having maps with Long keys

Hello, I am using zjsonpatch in a project and I really like it.

I ran some of our data through it and came across a strange error. I have not looked at the zjsonpatch code yet but from the stacktrace it looks like it is doing Integer.parseInt() on that Long value used as a key in the map.

The stacktrace:

Exception in thread "main" java.lang.NumberFormatException: For input string: "2407321151"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.base/java.lang.Integer.parseInt(Integer.java:652)
	at java.base/java.lang.Integer.parseInt(Integer.java:770)
	at com.flipkart.zjsonpatch.JsonDiff.isAllowed(JsonDiff.java:104)
	at com.flipkart.zjsonpatch.JsonDiff.introduceCopyOperation(JsonDiff.java:76)
	at com.flipkart.zjsonpatch.JsonDiff.asJson(JsonDiff.java:60)
	at com.flipkart.zjsonpatch.JsonDiff.asJson(JsonDiff.java:38)
	at JsonDiffTest.main(JsonDiffTest.java:33)

Code to replicate the issue:

public class JsonDiffTest {
    public static void main(String[] args) throws IOException {
        String a = "{\n" +
                "    \"map\": {\n" +
                "      \"3000000000\": {\n" +
                "        \"field\": 3100000000,\n" +
                "        \"otherField\": 0\n" +
                "      }\n" +
                "    }\n" +
                "  }";

        String b = "{\n" +
                "    \"map\": {\n" +
                "      \"3000000000\": {\n" +
                "        \"field\": 3100000000,\n" +
                "        \"otherField\": 0,\n" +
                "        \"extraField\": 0\n" +
                "      }\n" +
                "    }\n" +
                "  }";

        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonA = mapper.readTree(a);
        JsonNode jsonB = mapper.readTree(b);

        JsonDiff.asJson(jsonA, jsonB); // causes above stacktrace
    }
}

What makes it strange is that

  • if you remove either of otherField or extraField it works.
  • if you change the value of either of the fields with value 0 to anything else it works too.

Bug - compared to json

From the version (0.3.*) when comparing two json differing only in one verse, I get "copy" error instead of "add"
e.g
{"data1":"data1","value":null,"dataList":[],"data3":{"amount":17.2,"currency":"EU"},"type":"type"}

{"__data":null,"data1":"data1","value":null,"dataList":[],"data3":"amount":17.2,"currency":"EU"},"type":"type"}

result :
[{"op":"copy","from":"/type","path":"/__data"}]

Run custom code while applying a patch

I'm looking for a mechanism allowing to run custom code while applying a patch to a JSON document.

My use case (simplified) is following:

  • I have complex documents (in XML) that I need to keep in sync with an external system (that I don't have control).
  • Suppose that a document has following structure:
document:
- forward
- preface
- chapters:
   - name: Chapter 1
     sections:
     - name: Section 1
       paragraphs:
         - id: 1
            content: Some arbitrary text
...
  • Sometime, I have to update a document then synchronise it with the external system. However, this external system does not allow me to update the document in one shot but only partially in a request. I can call multiple its web services to update each part of the document, then finally, I can confirm the previous changes or abort. An exemple of its web services is:
- startEditing()
- updatePreface(id, content, ...)
- updateSection(id, name, ...)
- updateParagraph(id, content, ...)
- confirmChanges()
- abortChanges()
  • To minimise number of requests sending to the external system, I intend to convert a original document to JSON, compare them with its modified version to generate a JSON patch. While applying the patch, I will map each patch operation (add/remove/replace,...) to a web service of external system.

I've look at the code, specifically JsonPatch and ApplyProcessor. It seems that there are not yet a way to inject a custom JsonPatchProcessor or some kind of listener/hook in this library. I wonder if you intend to add this functionality any time soon? Or if you see another ways to implement my use case, it will be great too.

Thanks for your help.

Support for "copy" and "test" operations

Hi. I was wondering why there seemed to be no support for "copy" and "test" operations (both specified by the RFC).
Is it currently in development ? If not, is it planned ?

1.0 <> 1

If I compare the following 2 fragments:

expected:

{
    "value" : 1.0
}

test data:

{
    "value" : 1
}

zjsonpatch considers this different and generates a As far as I am aware, in JavaScript/JSON there is no distinction between integers and floats/doubles: they are all considered 'numbers'.

zjsonpatch should not generate a "replace" patch node for this.

Diff Replace - does not contain original value

When doing a diff between two jsons, for the REPLACE operation the fieldChanges should contain also the original value. I can work on this feature as I think it brings value to this library.

Taking too much

Hi,
I am using jsonpatch in my application to compare the json files and thereby generate comparison report of the same.
In the jsonpatch there one code present to create the matrix in lenth() method in com.topologi.diffx.algorithm.DiffXFitopsy.java.
It has nested for loop because of which my application is taking very long time and sometimes gets stuck badly.
Can you plz help me out I am badly stuck and need to resolve this issue ASAP

bug in json array removed.

If i compare the below two jsons,
Json 1:
{"a":{"b":[{"value":0},{"value":1},{"value":2}]}}

Json 2:
{"a":{"b":[]}}

The result I am getting is,
[{"op":"remove","path":"/a/b/0"},{"op":"remove","path":"/a/b/0"},{"op":"remove","path":"/a/b/0"}]

The actual result should be

[{"op":"remove","path":"/a/b/0"},{"op":"remove","path":"/a/b/1"},{"op":"remove","path":"/a/b/2"}]

Normalize to move & copy sometimes produces broken patches

We have been keeping a version history of documents stored into MongoDB by storing every single version of the whole documents into a separate journal collection. This journal is starting to grow uncontrollably and since mongodb stores json documents, we're investigating changing the journal format to jsonpatches to reduce the size of the collection.

We have evaluated zjsonpatch library and found that in a vast majority of cases it works flawlessly: We can calculate patches between hundreds of document versions and apply them in original order to produce a document that is identical with the one we have in mongodb.

In fact, if we use DiffFlags.dontNormalizeOpIntoMoveAndCopy() there are no problems whatsoever! The only caveat with this is that the size of the colleciton is 1,5 times larger with this approach but it is a fact we can live with.

However, in a very small minority (we're talking fractions of a percent) of cases, without DiffFlags.dontNormalizeOpIntoMoveAndCopy() a sufficiently large change in document between versions produces a patch that cannot be applied. We've got several different error messages in these cases, such as jsonPatch.noSuchPath, parent is not a container etc.

Common to all these cases is that changes in document both reorder some nested object arrays and also at the same time remove fields inside those reordered array items.

Typically this is the case when we remove a field in a domain object class whose instances are stored as an unordered collection such as a set inside other domain objects. Removing the field from all instances and saving the document changes nested object identities and collection's order gets shuffled which produces a very large jsonpatch.

I've been trying to produce a simplified failing test case for your inspection but no luck so far. I'll let you know if I can reproduce the problem with sufficiently small data.

copy/replace in one patch doc

If I start with the following JSON doc:
{"profiles":{"abc":[],"def":[{"hello":"world"}]}}

and apply the following patch:
[{"op":"copy","from":"/profiles/def/0", "path":"/profiles/def/0"},{"op":"replace","path":"/profiles/def/0/hello","value":"world2"}]

I get the following output:
{"profiles":{"abc":[],"def":[{"hello":"world2"},{"hello":"world2"}]}}

That is, the "replace" operation was applied to both array elements.
(Or possibly, the "replace" was applied before the "copy".)

Applying the "copy" and then the "replace" in 2 separate patch docs works as expected.

The following on-line tool [https://json-schema-validator.herokuapp.com/jsonpatch.jsp]
agrees the original output should have been
{"profiles":{"abc":[],"def":[{"hello":"world2"},{"hello":"world"}]}}
with the "replace" applied only to array element 0.

Thanks for a great library, btw!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.