You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rs...@apache.org on 2016/11/25 13:36:58 UTC

[2/2] johnzon git commit: JOHNZON-97 implemented JsonPatch

JOHNZON-97 implemented JsonPatch


Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/74cb9088
Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/74cb9088
Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/74cb9088

Branch: refs/heads/JSONP-1.1
Commit: 74cb90884b51861361929cb6f5bcb32ca154a96c
Parents: 24fe453
Author: Reinhard Sandtner <rs...@apache.org>
Authored: Thu Nov 24 00:11:42 2016 +0100
Committer: Reinhard Sandtner <rs...@apache.org>
Committed: Fri Nov 25 14:35:56 2016 +0100

----------------------------------------------------------------------
 .../org/apache/johnzon/core/JsonPatchImpl.java  |  23 +
 .../org/apache/johnzon/core/JsonPatchTest.java  | 920 ++++++++++++++++++-
 2 files changed, 931 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/johnzon/blob/74cb9088/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
index 352af88..0aaedf9 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
@@ -22,6 +22,7 @@ package org.apache.johnzon.core;
 import java.util.Arrays;
 import java.util.List;
 
+import javax.json.JsonException;
 import javax.json.JsonPatch;
 import javax.json.JsonPointer;
 import javax.json.JsonStructure;
@@ -42,12 +43,34 @@ class JsonPatchImpl implements JsonPatch {
 
         //X TODO JsonPointer should use generics like JsonPatch
         JsonStructure patched = target;
+
         for (PatchValue patch : patches) {
 
             switch (patch.operation) {
                 case ADD:
                     patched = patch.path.add(patched, patch.value);
                     break;
+                case REMOVE:
+                    patched = patch.path.remove(patched);
+                    break;
+                case REPLACE:
+                    // first remove the existing element and then add the new value
+                    patched = patch.path.add(patch.path.remove(patched), patch.value);
+                    break;
+                case MOVE:
+                    JsonValue valueToMove = patch.from.getValue(patched);
+                    patched = patch.path.add(patch.from.remove(patched), valueToMove);
+                    break;
+                case COPY:
+                    JsonValue toCopy = patch.from.getValue(patched);
+                    patched = patch.path.add(patched, toCopy);
+                    break;
+                case TEST:
+                    JsonValue toTest = patch.path.getValue(patched);
+                    if (!toTest.equals(patch.value)) {
+                        throw new JsonException("JsonPatchOperation.TEST fails! Values are not equal");
+                    }
+                    break;
                 default:
                     throw new IllegalStateException("unsupported operation: " + patch.operation);
             }

http://git-wip-us.apache.org/repos/asf/johnzon/blob/74cb9088/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchTest.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchTest.java
index a303893..b4665ed 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchTest.java
@@ -18,16 +18,22 @@
  */
 package org.apache.johnzon.core;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.io.StringReader;
-import java.io.StringWriter;
+import org.junit.Test;
 
 import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonException;
 import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import java.io.StringReader;
+import java.io.StringWriter;
 
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
 
 public class JsonPatchTest {
 
@@ -37,21 +43,911 @@ public class JsonPatchTest {
         JsonObject object = Json.createReader(new StringReader("{ \"foo\": \"bar\" }"))
                                 .readObject();
 
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/baz"),
+                                                                             null, // no from
+                                                                             new JsonStringImpl("qux")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertEquals("bar", patched.getString("foo"));
+        assertEquals("qux", patched.getString("baz"));
+
+        assertEquals("{\"foo\":\"bar\",\"baz\":\"qux\"}", toJsonString(patched));
+    }
+
+    @Test
+    public void testAddArrayElementWithIndex() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("baz"))
+                                .build();
 
         JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
-                                                                           Json.createJsonPointer("/baz"),
-                                                                           null, // no from
-                                                                           new JsonStringImpl("qux")));
+                                                                             Json.createJsonPointer("/foo/1"),
+                                                                             null, // no from
+                                                                             new JsonStringImpl("qux")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertNotNull(array);
+        assertEquals("bar", array.getString(0));
+        assertEquals("qux", array.getString(1));
+        assertEquals("baz", array.getString(2));
+
+        assertEquals("{\"foo\":[\"bar\",\"qux\",\"baz\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testAddArrayElementAppend() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("baz"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/foo/-"),
+                                                                             null, // no from
+                                                                             new JsonStringImpl("qux")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertNotNull(array);
+        assertEquals("bar", array.getString(0));
+        assertEquals("baz", array.getString(1));
+        assertEquals("qux", array.getString(2));
+
+        assertEquals("{\"foo\":[\"bar\",\"baz\",\"qux\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testAddArrayElementPlainArray() {
+        JsonArray array = Json.createArrayBuilder()
+                              .add("bar")
+                              .add("baz")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/-"),
+                                                                             null, // no from
+                                                                             new JsonStringImpl("qux")));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertNotSame(array, patched);
+        assertEquals("bar", patched.getString(0));
+        assertEquals("baz", patched.getString(1));
+        assertEquals("qux", patched.getString(2));
+
+        assertEquals("[\"bar\",\"baz\",\"qux\"]", toJsonString(patched));
+    }
+
+    @Test(expected = JsonException.class)
+    public void testAddNonexistentTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/baz/bat"),
+                                                                             null, // no from
+                                                                             new JsonStringImpl("qux")));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testAddArrayIndexOutOfBounds() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("bar")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/5"),
+                                                                             null,
+                                                                             new JsonStringImpl("baz")));
+
+        patch.apply(array);
+    }
+
+
+    @Test
+    public void testRemoveObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("baz", "qux")
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/baz"),
+                                                                             null,
+                                                                             null));
 
         JsonObject patched = patch.apply(object);
         assertNotNull(patched);
         assertEquals("bar", patched.getString("foo"));
+        assertFalse("patched JsonObject must no contain \"baz\"", patched.containsKey("baz"));
+
+        assertEquals("{\"foo\":\"bar\"}", toJsonString(patched));
+    }
+
+    @Test
+    public void testRemoveArrayElement() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("qux")
+                                                .add("baz"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/foo/1"),
+                                                                             null,
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertNotNull(array);
+        assertEquals(2, array.size());
+        assertEquals("bar", array.getString(0));
+        assertEquals("baz", array.getString(1));
+
+        assertEquals("{\"foo\":[\"bar\",\"baz\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testRemoveArrayElementPlainArray() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("bar")
+                              .add("qux")
+                              .add("baz")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/1"),
+                                                                             null,
+                                                                             null));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertEquals(2, patched.size());
+        assertEquals("bar", patched.getString(0));
+        assertEquals("baz", patched.getString(1));
+
+        assertEquals("[\"bar\",\"baz\"]", toJsonString(patched));
+    }
+
+    @Test(expected = JsonException.class)
+    public void testRemoveObjectElementNonexistentTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .add("baz", "qux")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/nomatch"),
+                                                                             null,
+                                                                             null));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testRemoveArrayElementIndexOutOfBounds() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("bar")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/5"),
+                                                                             null,
+                                                                             null));
+
+        patch.apply(array);
+    }
+
+
+    @Test
+    public void testReplacingObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("baz", "qux")
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REPLACE,
+                                                                             Json.createJsonPointer("/baz"),
+                                                                             null,
+                                                                             new JsonStringImpl("boo")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+        assertEquals("boo", patched.getString("baz"));
+        assertEquals("bar", patched.getString("foo"));
+
+        assertEquals("{\"foo\":\"bar\",\"baz\":\"boo\"}", toJsonString(patched));
+    }
+
+    @Test
+    public void testReplacingArrayElement() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("qux"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REPLACE,
+                                                                             Json.createJsonPointer("/foo/1"),
+                                                                             null,
+                                                                             new JsonStringImpl("boo")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertNotNull(array);
+        assertNotSame(object.getJsonArray("foo"), array);
+        assertEquals(2, array.size());
+        assertEquals("bar", array.getString(0));
+        assertEquals("boo", array.getString(1));
+
+        assertEquals("{\"foo\":[\"bar\",\"boo\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testReplacingArrayElementPlainArray() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("bar")
+                              .add("qux")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REPLACE,
+                                                                             Json.createJsonPointer("/0"),
+                                                                             null,
+                                                                             new JsonStringImpl("boo")));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertNotSame(array, patched);
+        assertEquals(2, patched.size());
+        assertEquals("boo", patched.getString(0));
+        assertEquals("qux", patched.getString(1));
+
+        assertEquals("[\"boo\",\"qux\"]", toJsonString(patched));
+    }
+
+    @Test(expected = JsonException.class)
+    public void testReplacingObjectMemberNonexistingTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REPLACE,
+                                                                             Json.createJsonPointer("/nomatch"),
+                                                                             null,
+                                                                             new JsonStringImpl("notneeded")));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testReplacingArrayElementIndexOutOfBounds() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("foo")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.REPLACE,
+                                                                             Json.createJsonPointer("/1"),
+                                                                             null,
+                                                                             new JsonStringImpl("notneeded")));
+
+        patch.apply(array);
+    }
+
+
+    @Test
+    public void testMovingObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createObjectBuilder()
+                                                .add("bar", "baz")
+                                                .add("waldo", "fred"))
+                                .add("qux", Json.createObjectBuilder()
+                                                .add("corge", "grault"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/qux/thud"),
+                                                                             Json.createJsonPointer("/foo/waldo"),
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+
+        JsonObject foo = patched.getJsonObject("foo");
+        assertNotNull(foo);
+        assertEquals("baz", foo.getString("bar"));
+        assertFalse("JsonObject with key 'foo' must not contain 'waldo'", foo.containsKey("waldo"));
+
+        JsonObject qux = patched.getJsonObject("qux");
+        assertNotNull(qux);
+        assertEquals("grault", qux.getString("corge"));
+        assertEquals("fred", qux.getString("thud"));
+
+        assertEquals("{\"foo\":{\"bar\":\"baz\"},\"qux\":{\"corge\":\"grault\",\"thud\":\"fred\"}}", toJsonString(patched));
+    }
+
+    @Test
+    public void testMovingArrayElement() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("all")
+                                                .add("grass")
+                                                .add("cows")
+                                                .add("eat"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/foo/3"),
+                                                                             Json.createJsonPointer("/foo/1"),
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertNotNull(array);
+        assertEquals("all", array.getString(0));
+        assertEquals("cows", array.getString(1));
+        assertEquals("eat", array.getString(2));
+        assertEquals("grass", array.getString(3));
+
+        assertEquals("{\"foo\":[\"all\",\"cows\",\"eat\",\"grass\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testMovingArrayElementPlainArray() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("two")
+                              .add("three")
+                              .add("four")
+                              .add("one")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/0"),
+                                                                             Json.createJsonPointer("/3"),
+                                                                             null));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertNotSame(array, patched);
+        assertEquals("one", patched.getString(0));
+        assertEquals("two", patched.getString(1));
+        assertEquals("three", patched.getString(2));
+        assertEquals("four", patched.getString(3));
+    }
+
+    @Test
+    public void testMovingArrayElementToObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("one")
+                                                .add("two")
+                                                .add("dog"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/bar"),
+                                                                             Json.createJsonPointer("/foo/2"),
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertEquals(2, patched.size());
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertEquals(2, array.size());
+        assertEquals("one", array.getString(0));
+        assertEquals("two", array.getString(1));
+
+        assertEquals("dog", patched.getString("bar"));
+
+        assertEquals("{\"foo\":[\"one\",\"two\"],\"bar\":\"dog\"}", toJsonString(patched));
+    }
+
+    @Test(expected = JsonException.class)
+    public void testMovingObjectMemberNonexistingFrom() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/baz"),
+                                                                             Json.createJsonPointer("/nomatch"),
+                                                                             null));
+
+        patch.apply(object);
+
+    }
+
+    @Test(expected = JsonException.class)
+    public void testMovingObjectMemberNonexistingTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/nomatch/child"),
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testMovingObjectMemberMoveToSubFrom() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("object", Json.createObjectBuilder()
+                                                   .add("key", "value"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/object/key"),
+                                                                             Json.createJsonPointer("/object"),
+                                                                             null));
+
+        patch.apply(object);
+    }
+
+
+    @Test
+    public void testCopyObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                               .add("foo", "bar")
+                               .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/baz"),
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertEquals(2, patched.size());
+        assertEquals("bar", patched.getString("foo"));
+        assertEquals("bar", patched.getString("baz"));
+
+        assertEquals("{\"foo\":\"bar\",\"baz\":\"bar\"}", toJsonString(patched));
+    }
+
+    @Test
+    public void testCopyArrayMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("baz"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                                 Json.createJsonPointer("/foo/-"),
+                                                                                 Json.createJsonPointer("/foo/0"),
+                                                                                 null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+
+        JsonArray array = patched.getJsonArray("foo");
+        assertEquals(3, array.size());
+        assertEquals("bar", array.getString(0));
+        assertEquals("baz", array.getString(1));
+        assertEquals("bar", array.getString(2));
+
+        assertEquals("{\"foo\":[\"bar\",\"baz\",\"bar\"]}", toJsonString(patched));
+    }
+
+    @Test
+    public void testCopyArrayMemberPlainArray() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("foo")
+                              .add("bar")
+                              .build();
+
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/0"),
+                                                                             Json.createJsonPointer("/1"),
+                                                                             null));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertNotSame(array, patched);
+        assertEquals(3, patched.size());
+        assertEquals("bar", patched.getString(0));
+        assertEquals("foo", patched.getString(1));
+        assertEquals("bar", patched.getString(2));
+
+        assertEquals("[\"bar\",\"foo\",\"bar\"]", toJsonString(patched));
+    }
+
+    @Test
+    public void testCopyObjectMemberToObjectMember() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("name", "Hugo")
+                                .add("partner", Json.createObjectBuilder()
+                                                    .add("name", "Leia")
+                                                    .add("partner", JsonValue.EMPTY_JSON_OBJECT))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/partner/partner/name"),
+                                                                             Json.createJsonPointer("/name"),
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+        assertEquals("Hugo", patched.getString("name"));
+
+        JsonObject partner = patched.getJsonObject("partner");
+        assertEquals("Leia", partner.getString("name"));
+
+        JsonObject parent = partner.getJsonObject("partner");
+        assertEquals(patched.getString("name"), parent.getString("name"));
+
+        assertEquals("{\"name\":\"Hugo\",\"partner\":{\"name\":\"Leia\",\"partner\":{\"name\":\"Hugo\"}}}", toJsonString(patched));
+    }
+
+    @Test(expected = JsonException.class)
+    public void testCopyObjectMemberFromNonexistentTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/notneeded"),
+                                                                             Json.createJsonPointer("/nomatch"),
+                                                                             null));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testCopyObjectMemberToNonexistingTarget() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/path/nomatch"),
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null));
+
+        patch.apply(object);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testCopyArrayMemberFromIndexOutOfBounds() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("foo")
+                              .add("bar")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/-"),
+                                                                             Json.createJsonPointer("/2"),
+                                                                             null));
+
+        patch.apply(array);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testCopyArrayMemberToIndexOutOfBounds() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("foo")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/1"),
+                                                                             Json.createJsonPointer("/-"),
+                                                                             null));
+
+        patch.apply(array);
+    }
+
+
+    @Test
+    public void testTestingObjectMemberValueSuccess() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "qux")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null,
+                                                                             new JsonStringImpl("qux")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertSame(object, patched);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testTestingObjectMemberValueFailed() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "qux")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null,
+                                                                             Json.createArrayBuilder().build()));
+
+        patch.apply(object);
+    }
+
+    @Test
+    public void testTestingArrayAsObjectMemberSuccess() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("name", "Thor")
+                                .add("parents", Json.createArrayBuilder()
+                                                    .add("Odin")
+                                                    .add("Forjgyn"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/parents"),
+                                                                             null,
+                                                                             Json.createArrayBuilder() // yessss, we really want to create a new JsonArray ;)
+                                                                                 .add("Odin")
+                                                                                 .add("Forjgyn")
+                                                                                 .build()));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertSame(object, patched);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testTestingArrayAsObjectMemberFailed() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("magic", "array")
+                                .add("numbers", Json.createArrayBuilder()
+                                                    .add(1)
+                                                    .add(2))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/numbers"),
+                                                                             null,
+                                                                             Json.createArrayBuilder() // different ordering
+                                                                                 .add(2)
+                                                                                 .add(1)
+                                                                                 .build()));
+
+        patch.apply(object);
+    }
+
+    @Test
+    public void testTestingArrayElementSuccess() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", Json.createArrayBuilder()
+                                                .add("bar")
+                                                .add("baz"))
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/foo/1"),
+                                                                             null,
+                                                                             new JsonStringImpl("baz")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertSame(object, patched);
+    }
+
+    @Test
+    public void testTestingArrayElementPlainArraySuccess() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add("foo")
+                              .add("bar")
+                              .add("qux")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/2"),
+                                                                             null,
+                                                                             new JsonStringImpl("qux")));
+
+        JsonArray patched = patch.apply(array);
+        assertNotNull(patched);
+        assertSame(array, patched);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testTestingArrayElementPlainArrayFailed() {
+
+        JsonArray array = Json.createArrayBuilder()
+                              .add(1)
+                              .add("2")
+                              .add("qux")
+                              .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/0"),
+                                                                             null,
+                                                                             new JsonStringImpl("bar")));
+
+        patch.apply(array);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testTestingObjectMemeberNonexistentTarget() {
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/nomatch"),
+                                                                             null,
+                                                                             JsonValue.EMPTY_JSON_OBJECT));
+
+        patch.apply(JsonValue.EMPTY_JSON_OBJECT);
+    }
+
+    @Test(expected = JsonException.class)
+    public void testTestingArrayElementIndexOutOfBounds() {
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.TEST,
+                                                                             Json.createJsonPointer("/3"),
+                                                                             null,
+                                                                             JsonValue.EMPTY_JSON_OBJECT));
+
+        patch.apply(JsonValue.EMPTY_JSON_ARRAY);
+    }
+
+
+    @Test
+    public void testAddObjectMemberAlreadyExists() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("foo", "bar")
+                                .add("baz", "qux")
+                                .build();
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/foo"),
+                                                                             null,
+                                                                             new JsonStringImpl("abcd")));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+        assertEquals("abcd", patched.getString("foo"));
         assertEquals("qux", patched.getString("baz"));
 
-        StringWriter writer = new StringWriter();
-        Json.createWriter(writer).write(patched);
+        assertEquals("{\"foo\":\"abcd\",\"baz\":\"qux\"}", toJsonString(patched));
+    }
 
-        assertEquals("{\"foo\":\"bar\",\"baz\":\"qux\"}", writer.toString());
+    @Test
+    public void testAddArrayElementToEmptyArray() {
+
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/-"),
+                                                                             null,
+                                                                             new JsonStringImpl("foo")));
+
+        JsonArray patched = patch.apply(JsonValue.EMPTY_JSON_ARRAY);
+        assertNotNull(patched);
+        assertEquals(1, patched.size());
+        assertEquals("foo", patched.getString(0));
+    }
+
+    @Test
+    public void testPatchWithMoreOperations() {
+
+        JsonObject object = Json.createObjectBuilder()
+                                .add("family", Json.createObjectBuilder()
+                                                   .add("children", JsonValue.EMPTY_JSON_ARRAY))
+                                .build();
+
+        // i know this can be done with PatchBuilder but
+        // currently it's not implemented and its fun ;)
+        JsonPatchImpl patch = new JsonPatchImpl(new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/family/father"),
+                                                                             null,
+                                                                             Json.createObjectBuilder()
+                                                                                 .add("name", "Gaio Modry Effect")
+                                                                                 .build()),
+                                                new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/family/mother"),
+                                                                             null,
+                                                                             Json.createObjectBuilder()
+                                                                                 .add("name", "Cassius vom Hause Clarabella")
+                                                                                 .build()),
+                                                new JsonPatchImpl.PatchValue(JsonPatchOperation.MOVE,
+                                                                             Json.createJsonPointer("/family/children/0"),
+                                                                             Json.createJsonPointer("/family/mother"),
+                                                                             null),
+                                                new JsonPatchImpl.PatchValue(JsonPatchOperation.ADD,
+                                                                             Json.createJsonPointer("/family/mother"),
+                                                                             null,
+                                                                             Json.createObjectBuilder()
+                                                                                 .add("name", "Aimee vom Hause Clarabella")
+                                                                                 .build()),
+                                                new JsonPatchImpl.PatchValue(JsonPatchOperation.COPY,
+                                                                             Json.createJsonPointer("/pedigree"),
+                                                                             Json.createJsonPointer("/family"),
+                                                                             null),
+                                                new JsonPatchImpl.PatchValue(JsonPatchOperation.REMOVE,
+                                                                             Json.createJsonPointer("/family"),
+                                                                             null,
+                                                                             null));
+
+        JsonObject patched = patch.apply(object);
+        assertNotNull(patched);
+        assertNotSame(object, patched);
+
+        JsonObject pedigree = patched.getJsonObject("pedigree");
+        assertEquals("Gaio Modry Effect", pedigree.getJsonObject("father").getString("name"));
+        assertEquals("Aimee vom Hause Clarabella", pedigree.getJsonObject("mother").getString("name"));
+        assertEquals("Cassius vom Hause Clarabella", pedigree.getJsonArray("children").getJsonObject(0).getString("name"));
+
+        assertEquals("{\"pedigree\":{" +
+                     "\"children\":[" +
+                     "{\"name\":\"Cassius vom Hause Clarabella\"}]," +
+                     "\"mother\":{\"name\":\"Aimee vom Hause Clarabella\"}," +
+                     "\"father\":{\"name\":\"Gaio Modry Effect\"}}}", toJsonString(patched));
+    }
+
+
+
+    private static String toJsonString(JsonStructure value) {
+        StringWriter writer = new StringWriter();
+        Json.createWriter(writer).write(value);
+        return writer.toString();
     }
 
 }