Skip to content

Commit

Permalink
add "create" operation
Browse files Browse the repository at this point in the history
  • Loading branch information
hnguyen08 committed Mar 25, 2014
1 parent 20e7144 commit 35102db
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 70 deletions.
72 changes: 2 additions & 70 deletions src/main/java/com/github/fge/jsonpatch/AddOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.jsonpointer.JsonPointer;
import com.github.fge.jackson.jsonpointer.ReferenceToken;
import com.github.fge.jackson.jsonpointer.TokenResolver;
import com.google.common.collect.Iterables;


/**
Expand Down Expand Up @@ -64,75 +59,12 @@
* </pre>
*/
public final class AddOperation
extends PathValueOperation
extends AdditionOperation
{
private static final ReferenceToken LAST_ARRAY_ELEMENT
= ReferenceToken.fromRaw("-");

@JsonCreator
public AddOperation(@JsonProperty("path") final JsonPointer path,
@JsonProperty("value") final JsonNode value)
{
super("add", path, value);
}

@Override
public JsonNode apply(final JsonNode node)
throws JsonPatchException
{
if (path.isEmpty())
return value;

/*
* Check the parent node: it must exist and be a container (ie an array
* or an object) for the add operation to work.
*/
final JsonNode parentNode = path.parent().path(node);
if (parentNode.isMissingNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchParent"));
if (!parentNode.isContainerNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.parentNotContainer"));
return parentNode.isArray()
? addToArray(path, node)
: addToObject(path, node);
}

private JsonNode addToArray(final JsonPointer path, final JsonNode node)
throws JsonPatchException
{
final JsonNode ret = node.deepCopy();
final ArrayNode target = (ArrayNode) path.parent().get(ret);
final TokenResolver<JsonNode> token = Iterables.getLast(path);

if (token.getToken().equals(LAST_ARRAY_ELEMENT)) {
target.add(value);
return ret;
}

final int size = target.size();
final int index;
try {
index = Integer.parseInt(token.toString());
} catch (NumberFormatException ignored) {
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.notAnIndex"));
}

if (index < 0 || index > size)
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchIndex"));

target.insert(index, value);
return ret;
}

private JsonNode addToObject(final JsonPointer path, final JsonNode node)
{
final JsonNode ret = node.deepCopy();
final ObjectNode target = (ObjectNode) path.parent().get(ret);
target.put(Iterables.getLast(path).getToken().getRaw(), value);
return ret;
super("add", path, value, true);
}
}
102 changes: 102 additions & 0 deletions src/main/java/com/github/fge/jsonpatch/AdditionOperation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.github.fge.jsonpatch;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.jsonpointer.JsonPointer;
import com.github.fge.jackson.jsonpointer.ReferenceToken;
import com.github.fge.jackson.jsonpointer.TokenResolver;
import com.google.common.collect.Iterables;

/**
* Represents an operation that can add a {@code value} given a {@code path}.
*/
public abstract class AdditionOperation
extends PathValueOperation
{
private static final ReferenceToken LAST_ARRAY_ELEMENT
= ReferenceToken.fromRaw("-");

private boolean overwriteExisting;

/**
* @param op operation name
* @param path affected path
* @param value value to add
* @param overwriteExisting whether the operation is allowed to overwrite an existing value at the specified path
*/
protected AdditionOperation(final String op, final JsonPointer path, final JsonNode value, boolean overwriteExisting)
{
super(op, path, value);
this.overwriteExisting = overwriteExisting;
}

@Override
public JsonNode apply(final JsonNode node)
throws JsonPatchException
{
if (path.isEmpty())
return value;

/*
* Check the parent node: it must exist and be a container (ie an array
* or an object) for the addition operation to work.
*/
final JsonNode parentNode = path.parent().path(node);
if (parentNode.isMissingNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchParent"));
if (!parentNode.isContainerNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.parentNotContainer"));
return parentNode.isArray()
? addToArray(path, node)
: addToObject(path, node);
}

private JsonNode addToArray(final JsonPointer path, final JsonNode node)
throws JsonPatchException
{
final JsonNode ret = node.deepCopy();
final ArrayNode target = (ArrayNode) path.parent().get(ret);
final TokenResolver<JsonNode> token = Iterables.getLast(path);

if (token.getToken().equals(LAST_ARRAY_ELEMENT)) {
target.add(value);
return ret;
}

final int size = target.size();
final int index;
try {
index = Integer.parseInt(token.toString());
} catch (NumberFormatException ignored) {
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.notAnIndex"));
}

if (index < 0 || index > size)
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchIndex"));

target.insert(index, value);
return ret;
}

private JsonNode addToObject(final JsonPointer path, final JsonNode node)
throws JsonPatchException
{
if (!overwriteExisting)
{
final JsonNode existingNode = path.path(node);
if (!existingNode.isMissingNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.valueAtPathAlreadyExists"));
}

final JsonNode ret = node.deepCopy();
final ObjectNode target = (ObjectNode) path.parent().get(ret);
target.put(Iterables.getLast(path).getToken().getRaw(), value);
return ret;
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/github/fge/jsonpatch/CreateOperation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.github.fge.jsonpatch;

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;

/**
* JSON Patch {@code create} operation.
*
* <p>For this operation, {@code path} is the JSON Pointer where the value
* should be added, and {@code value} is the value to add.</p>
*
* <p>This operation behaves like {@code add}, except for JSON Objects,
* where it will raise an error if the target {@code path} points to an
* existing value. This is designed to prevent clients from actually
* overwriting values they don't think exist.</p>
*/
public final class CreateOperation
extends AdditionOperation
{
@JsonCreator
public CreateOperation(@JsonProperty("path") final JsonPointer path,
@JsonProperty("value") final JsonNode value)
{
super("create", path, value, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@JsonSubTypes({
@Type(name = "add", value = AddOperation.class),
@Type(name = "copy", value = CopyOperation.class),
@Type(name = "create", value = CreateOperation.class),
@Type(name = "move", value = MoveOperation.class),
@Type(name = "remove", value = RemoveOperation.class),
@Type(name = "replace", value = ReplaceOperation.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ jsonPatch.notAnIndex=reference token is not an array index
jsonPatch.noSuchIndex=no such index in target array
jsonPatch.noSuchPath=no such path in target JSON document
jsonPatch.parentNotContainer=parent of path to add to is not a container
jsonPatch.valueAtPathAlreadyExists=value at path already exists
jsonPatch.valueTestFailure=value differs from expectations
mergePatch.notContainer=value is neither an object or an array (found %s)
13 changes: 13 additions & 0 deletions src/test/java/com/github/fge/jsonpatch/CreateOperationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.fge.jsonpatch;

import java.io.IOException;

public final class CreateOperationTest
extends JsonPatchOperationTest
{
public CreateOperationTest()
throws IOException
{
super("create");
}
}
91 changes: 91 additions & 0 deletions src/test/resources/jsonpatch/create.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"errors": [
{
"op": { "op": "create", "path": "/a/b/c", "value": 1 },
"node": { "a": "b" },
"message": "jsonPatch.noSuchParent"
},
{
"op": { "op": "create", "path": "/a", "value": 1 },
"node": { "a": "b" },
"message": "jsonPatch.valueAtPathAlreadyExists"
},
{
"op": { "op": "create", "path": "/a", "value": 1 },
"node": { "a": null },
"message": "jsonPatch.valueAtPathAlreadyExists"
},
{
"op": { "op": "create", "path": "/obj/inner/b", "value": [ 1, 2 ] },
"node": {
"obj": {
"inner": {
"a": "hello",
"b": "world"
}
}
},
"message": "jsonPatch.valueAtPathAlreadyExists"
},
{
"op": { "op": "create", "path": "/~1", "value": 1 },
"node": [],
"message": "jsonPatch.notAnIndex"
},
{
"op": { "op": "create", "path": "/3", "value": 1 },
"node": [ 1, 2 ],
"message": "jsonPatch.noSuchIndex"
},
{
"op": { "op": "create", "path": "/-2", "value": 1 },
"node": [ 1, 2 ],
"message": "jsonPatch.noSuchIndex"
},
{
"op": { "op": "create", "path": "/foo/f", "value": "bar" },
"node": { "foo": "bar" },
"message": "jsonPatch.parentNotContainer"
}
],
"ops": [
{
"op": { "op": "create", "path": "", "value": null },
"node": {},
"expected": null
},
{
"op": { "op": "create", "path": "/a", "value": "b" },
"node": {},
"expected": { "a": "b" }
},
{
"op": { "op": "create", "path": "/array/-", "value": 1 },
"node": { "array": [ 2, null, {}, 1 ] },
"expected": { "array": [ 2, null, {}, 1, 1 ] }
},
{
"op": { "op": "create", "path": "/array/2", "value": "hello" },
"node": { "array": [ 2, null, {}, 1] },
"expected": { "array": [ 2, null, "hello", {}, 1 ] }
},
{
"op": { "op": "create", "path": "/obj/inner/b", "value": [ 1, 2 ] },
"node": {
"obj": {
"inner": {
"a": "hello"
}
}
},
"expected": {
"obj": {
"inner": {
"a": "hello",
"b": [ 1, 2 ]
}
}
}
}
]
}

0 comments on commit 35102db

Please sign in to comment.