blob: fc9ff4e72d3e1f8815bc8738cd6e527ff23fea19 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.johnzon.core;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.StringReader;
import java.io.StringWriter;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonPatch;
import jakarta.json.JsonReader;
import jakarta.json.JsonValue;
import jakarta.json.JsonWriter;
import jakarta.json.stream.JsonGenerator;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
public class JsonPatchDiffTest {
@Test
public void nestedObjects() {
final String source = "{\n" +
" \"caseDiscussionDailySchedule\":{\n" +
" \"schedule\":{\n" +
" \"TUESDAY\":[\n" +
" {\n" +
" \"start\":\"07:00+03:00\",\n" +
" \"end\":\"08:00+03:00\"\n" +
" }\n" +
" ],\n" +
" \"MONDAY\":[\n" +
" {\n" +
" \"start\":\"07:00+03:00\",\n" +
" \"end\":\"08:00+03:00\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
final String operation = "[" +
"{\"op\":\"replace\",\"path\":\"/caseDiscussionDailySchedule/schedule/MONDAY/0/start\",\"value\":null}" +
"]";
final String expectedResult = "{\n" +
" \"caseDiscussionDailySchedule\":{\n" +
" \"schedule\":{\n" +
" \"TUESDAY\":[\n" +
" {\n" +
" \"start\":\"07:00+03:00\",\n" +
" \"end\":\"08:00+03:00\"\n" +
" }\n" +
" ],\n" +
" \"MONDAY\":[\n" +
" {\n" +
" \"end\":\"08:00+03:00\",\n" +
" \"start\":null\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
final JsonReader reader1 = Json.createReader(new StringReader(source));
final JsonObject src = reader1.readObject();
reader1.close();
final JsonReader reader2 = Json.createReader(new StringReader(operation));
final JsonArray op = reader2.readArray();
reader2.close();
final JsonObject result = Json.createPatch(op).apply(src);
final StringWriter formattedResult = new StringWriter();
final JsonWriter writer = Json.createWriterFactory(singletonMap(JsonGenerator.PRETTY_PRINTING, true))
.createWriter(formattedResult);
writer.write(result);
writer.close();
assertEquals(expectedResult, formattedResult.toString());
}
@Test
public void fromEmptyArray() {
final JsonObject from = Json.createObjectBuilder().add("testEmpty", JsonValue.EMPTY_JSON_ARRAY).build();
final JsonObject to = Json.createObjectBuilder()
.add("testEmpty", Json.createArrayBuilder().add("something"))
.build();
final JsonPatch diff = Json.createDiff(from, to);
assertEquals("[{\"op\":\"add\",\"path\":\"/testEmpty/0\",\"value\":\"something\"}]", diff.toString());
}
@Test
public void toEmptyArray() {
final JsonObject from = Json.createObjectBuilder()
.add("testEmpty", Json.createArrayBuilder().add("something"))
.build();
final JsonObject to = Json.createObjectBuilder().add("testEmpty", JsonValue.EMPTY_JSON_ARRAY).build();
final JsonPatch diff = Json.createDiff(from, to);
assertEquals("[{\"op\":\"remove\",\"path\":\"/testEmpty/0\"}]", diff.toString());
}
@Test
public void testAddDiff() {
// {"a":"xa"}
String jsonA = "{\"a\":\"xa\"}";
// {"a":"xa","b":"xb"}
String jsonB = "{\"a\":\"xa\",\"b\":\"xb\"}";
// this results in 1 diff operations:
// adding "b"
JsonPatch jsonPatch = Json.createDiff(Json.createReader(new StringReader(jsonA)).readObject(),
Json.createReader(new StringReader(jsonB)).readObject());
assertNotNull(jsonPatch);
JsonArray patchOperations = jsonPatch.toJsonArray();
assertNotNull(patchOperations);
assertEquals(1, patchOperations.size());
containsOperation(patchOperations, JsonPatch.Operation.ADD, "/b", Json.createValue("xb"));
}
@Test
public void testAddDiffNewObject() {
JsonObject target = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "value")
.add("ab", "another"))
.build();
JsonPatch patch = Json.createDiff(JsonValue.EMPTY_JSON_OBJECT, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.ADD, "/a", target.get("a"));
// now try to apply that patch.
JsonObject patched = patch.apply(JsonValue.EMPTY_JSON_OBJECT);
Assert.assertEquals(target, patched);
}
@Test
public void testAddDiffNewObjectWithEscaping() {
JsonObject target = Json.createObjectBuilder()
.add("a~/", Json.createObjectBuilder()
.add("esc/aped", "value")
.add("tilde", "another"))
.build();
JsonPatch patch = Json.createDiff(JsonValue.EMPTY_JSON_OBJECT, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.ADD, "/a~0~1", target.get("a"));
// now try to apply that patch.
JsonObject patched = patch.apply(JsonValue.EMPTY_JSON_OBJECT);
Assert.assertEquals(target, patched);
}
@Test
public void testAddDiffInNestedObject() {
JsonObject source = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "value"))
.build();
JsonObject target = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "value")
.add("bb", "another value"))
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.ADD, "/a/bb", Json.createValue("another value"));
}
@Test
public void testRemoveDiffObject() {
JsonObject source = Json.createObjectBuilder()
.add("a", "value")
.build();
JsonPatch patch = Json.createDiff(source, JsonValue.EMPTY_JSON_OBJECT);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.REMOVE, "/a");
}
@Test
public void testRemoveDiffNestedObject() {
JsonObject source = Json.createObjectBuilder()
.add("a", "value")
.add("nested", Json.createObjectBuilder()
.add("1", 1)
.add("2", 2))
.build();
{
JsonPatch patch = Json.createDiff(source, JsonValue.EMPTY_JSON_OBJECT);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(2, operations.size());
containsOperation(operations, JsonPatch.Operation.REMOVE, "/a");
containsOperation(operations, JsonPatch.Operation.REMOVE, "/nested");
}
{
JsonObject target = Json.createObjectBuilder()
.add("a", "value")
.add("nested", Json.createObjectBuilder()
.add("1", 1))
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.REMOVE, "/nested/2");
}
}
@Test
public void testDiffEqualObjects() {
JsonObject source = Json.createObjectBuilder()
.add("a", "value")
.build();
JsonObject target = Json.createObjectBuilder()
.add("a", "value")
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
assertEquals(0, patch.toJsonArray().size());
}
@Test
public void testDiffReplaceObject() {
JsonObject source = Json.createObjectBuilder()
.add("a", "value")
.build();
JsonObject target = Json.createObjectBuilder()
.add("a", "replaced")
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.REPLACE, "/a", Json.createValue("replaced"));
}
@Test
public void testDiffReplaceFromNestedObject() {
JsonObject source = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "value"))
.build();
JsonObject target = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "replaced"))
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.REPLACE, "/a/aa", Json.createValue("replaced"));
}
//X TODO add move support -> see https://issues.apache.org/jira/browse/JOHNZON-105
@Test
@Ignore
public void testDiffMoveValue() {
JsonObject source = Json.createObjectBuilder()
.add("a", Json.createObjectBuilder()
.add("aa", "valueToMove"))
.add("b", JsonValue.EMPTY_JSON_OBJECT)
.build();
JsonObject target = Json.createObjectBuilder()
.add("a", JsonValue.EMPTY_JSON_OBJECT)
.add("b", Json.createObjectBuilder()
.add("aa", "valueToMove"))
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
JsonObject operation = operations.getJsonObject(0);
assertEquals(JsonPatch.Operation.MOVE.operationName(), operation.getString("op"));
assertEquals("/a/aa", operation.getString("from"));
assertEquals("/b/aa", operation.getString("to"));
}
@Test
public void testAddValueToArray() {
JsonArray source = Json.createArrayBuilder()
.add("first")
.build();
JsonArray target = Json.createArrayBuilder()
.add("first")
.add("second")
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.ADD, "/1", Json.createValue("second"));
}
@Test
public void testAddObjectToArray() {
JsonArray source = Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("a", "a"))
.build();
JsonArray target = Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("a", "a"))
.add(Json.createObjectBuilder()
.add("a", "b"))
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.ADD, "/1", Json.createObjectBuilder()
.add("a", "b")
.build());
}
@Test
public void testRemoveValueFromArray() {
JsonArray source = Json.createArrayBuilder()
.add("a")
.add("b")
.build();
JsonArray target = Json.createArrayBuilder()
.add("b")
.build();
JsonPatch patch = Json.createDiff(source, target);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(2, operations.size());
containsOperation(operations, JsonPatch.Operation.REPLACE, "/0", Json.createValue("b"));
containsOperation(operations, JsonPatch.Operation.REMOVE, "/1");
}
@Test
public void testRemoveObjectFromArray() {
JsonArray source = Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("a", "a")
.add("b", "b"))
.build();
JsonPatch patch = Json.createDiff(source, JsonValue.EMPTY_JSON_ARRAY);
assertNotNull(patch);
JsonArray operations = patch.toJsonArray();
assertEquals(1, operations.size());
containsOperation(operations, JsonPatch.Operation.REMOVE, "/0");
}
//X TODO replaceValueInArray
//X TODO replaceObjectInArray
//X TODO Object in Array
//X TODO Array in Object
@Test
public void testComplexDiff() {
// {"a":"xa","b":2,"c":{"d":"xd"},"e":[1,2,3]}
String jsonA = "{\"a\":\"xa\",\"b\":2,\"c\":{\"d\":\"xd\"},\"e\":[1,2,3]}";
// {"a":"xa","c":{"d":"xd", "d2":"xd2"},"e":[1,3],"f":"xe"}
String jsonB = "{\"a\":\"xa\",\"c\":{\"d\":\"xd\", \"d2\":\"xd2\"},\"e\":[1,3],\"f\":\"xe\"}";
// this results in 5 diff operations:
// removing b, adding d2, replacing 2 with 3 from e, removing 3 from 3, adding f
JsonPatch jsonPatch = Json.createDiff(Json.createReader(new StringReader(jsonA)).readObject(),
Json.createReader(new StringReader(jsonB)).readObject());
assertNotNull(jsonPatch);
JsonArray patchOperations = jsonPatch.toJsonArray();
assertNotNull(patchOperations);
assertEquals(5, patchOperations.size());
containsOperation(patchOperations, JsonPatch.Operation.REMOVE, "/b");
containsOperation(patchOperations, JsonPatch.Operation.ADD, "/c/d2", Json.createValue("xd2"));
containsOperation(patchOperations, JsonPatch.Operation.REPLACE, "/e/1", Json.createValue(3));
containsOperation(patchOperations, JsonPatch.Operation.REMOVE, "/e/2");
containsOperation(patchOperations, JsonPatch.Operation.ADD, "/f", Json.createValue("xe"));
}
private void containsOperation(JsonArray patchOperations,
JsonPatch.Operation patchOperation,
String jsonPointer) {
containsOperation(patchOperations, patchOperation, jsonPointer, null);
}
private void containsOperation(JsonArray patchOperations,
JsonPatch.Operation patchOperation,
String jsonPointer,
JsonValue value) {
for (JsonValue operation : patchOperations) {
if (operation instanceof JsonObject &&
patchOperation.operationName().equalsIgnoreCase(((JsonObject) operation).getString("op")) &&
((JsonObject) operation).getString("path").equals(jsonPointer)) {
if (value != null) {
assertEquals(value, ((JsonObject) operation).get("value"));
}
return;
}
}
Assert.fail("patchOperations does not contain " + patchOperation + " " + jsonPointer);
}
}