From c54552fbc6596a16ba67b710c1a2540dc97b1ab0 Mon Sep 17 00:00:00 2001 From: Jessica Beller Date: Thu, 22 Dec 2016 13:34:00 -0800 Subject: [PATCH] Add remove? operation --- .../jsonpatch/ExtendedJsonPatchFactory.java | 1 + .../jsonpatch/operation/RemoveOperation.java | 73 +---------- .../operation/RemoveOperationBase.java | 118 ++++++++++++++++++ .../operation/RemoveOptionalOperation.java | 43 +++++++ .../RemoveOptionalOperationTest.java | 50 ++++++++ ...oveOptionalOperationSerializationTest.java | 34 +++++ .../resources/jsonpatch/extended/remove?.json | 25 ++++ 7 files changed, 275 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/github/fge/jsonpatch/operation/RemoveOperationBase.java create mode 100644 src/main/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperation.java create mode 100644 src/test/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperationTest.java create mode 100644 src/test/java/com/github/fge/jsonpatch/serialization/RemoveOptionalOperationSerializationTest.java create mode 100644 src/test/resources/jsonpatch/extended/remove?.json diff --git a/src/main/java/com/github/fge/jsonpatch/ExtendedJsonPatchFactory.java b/src/main/java/com/github/fge/jsonpatch/ExtendedJsonPatchFactory.java index c8126351..d1c76fe1 100644 --- a/src/main/java/com/github/fge/jsonpatch/ExtendedJsonPatchFactory.java +++ b/src/main/java/com/github/fge/jsonpatch/ExtendedJsonPatchFactory.java @@ -25,6 +25,7 @@ public static JsonPatchFactory create() new NamedType(CopyOperation.class, CopyOperation.OPERATION_NAME), new NamedType(MoveOperation.class, MoveOperation.OPERATION_NAME), new NamedType(RemoveOperation.class, RemoveOperation.OPERATION_NAME), + new NamedType(RemoveOptionalOperation.class, RemoveOptionalOperation.OPERATION_NAME), new NamedType(ReplaceOperation.class, ReplaceOperation.OPERATION_NAME), new NamedType(TestOperation.class, TestOperation.OPERATION_NAME), new NamedType(OmitOperation.class, OmitOperation.OPERATION_NAME), diff --git a/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperation.java b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperation.java index eaf57f7e..40e17fd8 100644 --- a/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperation.java +++ b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperation.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) + * Copyright (c) 2016, Jessica Beller (jbeller@box.com) * * This software is dual-licensed under: * @@ -21,23 +22,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.MissingNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jsonpatch.JsonPatchException; -import com.github.fge.jsonpatch.JsonPatchMessages; -import com.github.fge.jsonpatch.operation.JsonPatchOperation; -import com.github.fge.msgsimple.bundle.MessageBundle; -import com.github.fge.msgsimple.load.MessageBundles; -import com.google.common.collect.Iterables; - -import java.io.IOException; +import com.github.fge.jsonpatch.operation.policy.PathMissingPolicy; /** * JSON Path {@code remove} operation @@ -45,66 +32,14 @@ *

This operation only takes one pointer ({@code path}) as an argument. It * is an error condition if no JSON value exists at that pointer.

*/ -public final class RemoveOperation - implements JsonPatchOperation +public final class RemoveOperation extends RemoveOperationBase { public static final String OPERATION_NAME = "remove"; - protected static final MessageBundle BUNDLE - = MessageBundles.getBundle(JsonPatchMessages.class); - - protected final String op; - - protected final JsonPointer path; - @JsonCreator public RemoveOperation(@JsonProperty("path") final JsonPointer path) { - this.op = OPERATION_NAME; - this.path = path; + super(OPERATION_NAME, path, PathMissingPolicy.THROW); } - @Override - public JsonNode apply(final JsonNode node) - throws JsonPatchException - { - if (path.isEmpty()) - return MissingNode.getInstance(); - if (path.path(node).isMissingNode()) - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.noSuchPath")); - final JsonNode ret = node.deepCopy(); - final JsonNode parentNode = path.parent().get(ret); - final String raw = Iterables.getLast(path).getToken().getRaw(); - if (parentNode.isObject()) - ((ObjectNode) parentNode).remove(raw); - else - ((ArrayNode) parentNode).remove(Integer.parseInt(raw)); - return ret; - } - - @Override - public void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException, JsonProcessingException - { - jgen.writeStartObject(); - jgen.writeStringField("op", "remove"); - jgen.writeStringField("path", path.toString()); - jgen.writeEndObject(); - } - - @Override - public void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException, JsonProcessingException - { - serialize(jgen, provider); - } - - @Override - public String toString() - { - return "op: " + op + "; path: \"" + path + '"'; - } } diff --git a/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperationBase.java b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperationBase.java new file mode 100644 index 00000000..40298fab --- /dev/null +++ b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOperationBase.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com) + * Copyright (c) 2016, Jessica Beller (jbeller@box.com) + * + * This software is dual-licensed under: + * + * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any + * later version; + * - the Apache Software License (ASL) version 2.0. + * + * The text of this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, under + * the names LGPL-3.0.txt and ASL-2.0.txt respectively. + * + * Direct link to the sources: + * + * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt + * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt + */ + +package com.github.fge.jsonpatch.operation; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.MissingNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.github.fge.jsonpatch.JsonPatchException; +import com.github.fge.jsonpatch.JsonPatchMessages; +import com.github.fge.jsonpatch.operation.JsonPatchOperation; +import com.github.fge.jsonpatch.operation.policy.PathMissingPolicy; +import com.github.fge.msgsimple.bundle.MessageBundle; +import com.github.fge.msgsimple.load.MessageBundles; +import com.google.common.collect.Iterables; + +import java.io.IOException; + +/** + * RemoveOperationBase implements the basic concept of removing the requested path. + */ +public abstract class RemoveOperationBase + implements JsonPatchOperation +{ + protected static final MessageBundle BUNDLE + = MessageBundles.getBundle(JsonPatchMessages.class); + + private PathMissingPolicy pathMissingPolicy; + + protected final String op; + + protected final JsonPointer path; + + @JsonCreator + public RemoveOperationBase(final String op, + @JsonProperty("path") final JsonPointer path, + final PathMissingPolicy pathMissingPolicy) + { + this.op = op; + this.path = path; + this.pathMissingPolicy = pathMissingPolicy; + } + + @Override + public JsonNode apply(final JsonNode node) + throws JsonPatchException + { + final JsonNode ret = node.deepCopy(); + if (path.isEmpty()) + return MissingNode.getInstance(); + if (path.path(node).isMissingNode()) { + switch (pathMissingPolicy) { + case THROW: + throw new JsonPatchException(BUNDLE.getMessage( + "jsonPatch.noSuchPath")); + case SKIP: + return ret; + } + } + final JsonNode parentNode = path.parent().get(ret); + final String raw = Iterables.getLast(path).getToken().getRaw(); + if (parentNode.isObject()) + ((ObjectNode) parentNode).remove(raw); + else + ((ArrayNode) parentNode).remove(Integer.parseInt(raw)); + return ret; + } + + @Override + public void serialize(final JsonGenerator jgen, + final SerializerProvider provider) + throws IOException, JsonProcessingException + { + jgen.writeStartObject(); + jgen.writeStringField("op", op); + jgen.writeStringField("path", path.toString()); + jgen.writeEndObject(); + } + + @Override + public void serializeWithType(final JsonGenerator jgen, + final SerializerProvider provider, final TypeSerializer typeSer) + throws IOException, JsonProcessingException + { + serialize(jgen, provider); + } + + @Override + public String toString() + { + return "op: " + op + "; path: \"" + path + '"'; + } +} diff --git a/src/main/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperation.java b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperation.java new file mode 100644 index 00000000..7fe0089b --- /dev/null +++ b/src/main/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperation.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016, Jessica Beller (jbeller@box.com) + * + * This software is dual-licensed under: + * + * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any + * later version; + * - the Apache Software License (ASL) version 2.0. + * + * The text of this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, under + * the names LGPL-3.0.txt and ASL-2.0.txt respectively. + * + * Direct link to the sources: + * + * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt + * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt + */ + +package com.github.fge.jsonpatch.operation; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.github.fge.jsonpatch.operation.policy.PathMissingPolicy; + +/** + * JSON Path {@code remove?} operation + * + *

This operation will remove ({@code path}) if it exists.

+ */ +public final class RemoveOptionalOperation extends RemoveOperationBase +{ + public static final String OPERATION_NAME = "remove?"; + + @JsonCreator + public RemoveOptionalOperation(@JsonProperty("path") final JsonPointer path) + { + super(OPERATION_NAME, path, PathMissingPolicy.SKIP); + } + +} diff --git a/src/test/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperationTest.java b/src/test/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperationTest.java new file mode 100644 index 00000000..95b023f7 --- /dev/null +++ b/src/test/java/com/github/fge/jsonpatch/operation/RemoveOptionalOperationTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, Jessica Beller (jbeller@box.com) + * + * This software is dual-licensed under: + * + * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any + * later version; + * - the Apache Software License (ASL) version 2.0. + * + * The text of this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, under + * the names LGPL-3.0.txt and ASL-2.0.txt respectively. + * + * Direct link to the sources: + * + * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt + * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt + */ + +package com.github.fge.jsonpatch.operation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jackson.JacksonUtils; +import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.github.fge.jsonpatch.JsonPatchException; +import org.testng.annotations.Test; + +import java.io.IOException; + +import static org.testng.Assert.*; + +public final class RemoveOptionalOperationTest + extends ExtendedJsonPatchOperationTest +{ + public RemoveOptionalOperationTest() + throws IOException + { + super(RemoveOptionalOperation.OPERATION_NAME); + } + + @Test + public void removingRootReturnsMissingNode() + throws JsonPatchException + { + final JsonNode node = JacksonUtils.nodeFactory().nullNode(); + final JsonPatchOperation op = new RemoveOptionalOperation(JsonPointer.empty()); + final JsonNode ret = op.apply(node); + assertTrue(ret.isMissingNode()); + } +} diff --git a/src/test/java/com/github/fge/jsonpatch/serialization/RemoveOptionalOperationSerializationTest.java b/src/test/java/com/github/fge/jsonpatch/serialization/RemoveOptionalOperationSerializationTest.java new file mode 100644 index 00000000..33307e05 --- /dev/null +++ b/src/test/java/com/github/fge/jsonpatch/serialization/RemoveOptionalOperationSerializationTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, Jessica Beller (jbeller@box.com) + * + * This software is dual-licensed under: + * + * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any + * later version; + * - the Apache Software License (ASL) version 2.0. + * + * The text of this file and of both licenses is available at the root of this + * project or, if you have the jar distribution, in directory META-INF/, under + * the names LGPL-3.0.txt and ASL-2.0.txt respectively. + * + * Direct link to the sources: + * + * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt + * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt + */ + +package com.github.fge.jsonpatch.serialization; + +import com.github.fge.jsonpatch.operation.RemoveOptionalOperation; + +import java.io.IOException; + +public final class RemoveOptionalOperationSerializationTest + extends ExtendedJsonPatchOperationSerializationTest +{ + public RemoveOptionalOperationSerializationTest() + throws IOException + { + super(RemoveOptionalOperation.OPERATION_NAME); + } +} diff --git a/src/test/resources/jsonpatch/extended/remove?.json b/src/test/resources/jsonpatch/extended/remove?.json new file mode 100644 index 00000000..7f7c940e --- /dev/null +++ b/src/test/resources/jsonpatch/extended/remove?.json @@ -0,0 +1,25 @@ +{ + "errors": [], + "ops": [ + { + "op": { "op": "remove?", "path": "/x/y" }, + "node": { "x": { "a": "b", "y": {} } }, + "expected": { "x": { "a": "b" } } + }, + { + "op": { "op": "remove?", "path": "/0/2" }, + "node": [ [ "a", "b", "c"], "d", "e" ], + "expected": [ [ "a", "b" ], "d", "e" ] + }, + { + "op": { "op": "remove?", "path": "/x/0" }, + "node": { "x": [ "y", "z" ], "foo": "bar" }, + "expected": { "x": [ "z" ], "foo": "bar" } + }, + { + "op": { "op": "remove?", "path": "/doesNotExist" }, + "node": { "x": "y" }, + "expected": { "x": "y" } + } + ] +}