Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ targetCompatibility = "1.6"; // defaults to sourceCompatibility
dependencies {
provided(group: "com.google.code.findbugs", name: "jsr305",
version: "2.0.1");
compile(group: "com.github.fge", name: "jackson-coreutils",
version: "1.6");
compile(group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.4.4")
compile(group: "com.github.fge", name: "msg-simple", version: "1.1")
testCompile(group: "org.testng", name: "testng", version: "6.8.7") {
exclude(group: "junit", module: "junit");
exclude(group: "org.beanshell", module: "bsh");
Expand Down
27 changes: 13 additions & 14 deletions src/main/java/com/github/fge/jsonpatch/AddOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@

package com.github.fge.jsonpatch;

import static com.github.fge.jsonpatch.JacksonUtils.head;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonPointer;
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;


/**
* JSON Patch {@code add} operation
Expand Down Expand Up @@ -67,8 +64,9 @@
public final class AddOperation
extends PathValueOperation
{
private static final ReferenceToken LAST_ARRAY_ELEMENT
= ReferenceToken.fromRaw("-");

private static final JsonPointer EMPTY = JsonPointer.compile("");
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jackson JsonPointer does not have an empty method. For this reason we created an empty JsonPointer.

private static final String LAST_ARRAY_ELEMENT = "-";

@JsonCreator
public AddOperation(@JsonProperty("path") final JsonPointer path,
Expand All @@ -81,14 +79,15 @@ public AddOperation(@JsonProperty("path") final JsonPointer path,
public JsonNode apply(final JsonNode node)
throws JsonPatchException
{
if (path.isEmpty())
if (EMPTY.equals(path))
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);
final JsonNode parentNode = node.at(head(path));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jackson applies a JSonPointer over a JsonNode and not the inverse as used in jackson-utils.
Moreover Jackson JsonPointer does not implement the parent method. For this reason we have created a head method which do exactly the same as parent() method but using Jackson JsonPointer.

Side note: we have sent a PR to jackson-core to add support for head() method natively.


if (parentNode.isMissingNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchParent"));
Expand All @@ -104,10 +103,10 @@ 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);
final ArrayNode target = (ArrayNode)ret.at(head(path));
final String token = JacksonUtils.getLast(path);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns the last part of the jsonpointer. Again this operation does not exists directly in Jackson JsonPointer implementation and we have created an implementation for fixing this.


if (token.getToken().equals(LAST_ARRAY_ELEMENT)) {
if (LAST_ARRAY_ELEMENT.equals(token)) {
target.add(value);
return ret;
}
Expand All @@ -132,8 +131,8 @@ private JsonNode addToArray(final JsonPointer path, final JsonNode node)
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);
final ObjectNode target = (ObjectNode) ret.at(head(path));
target.put(JacksonUtils.getLast(path), value);
return ret;
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/github/fge/jsonpatch/CopyOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.fge.jackson.jsonpointer.JsonPointer;

/**
* JSON Patch {@code copy} operation
Expand Down Expand Up @@ -54,7 +54,7 @@ public CopyOperation(@JsonProperty("from") final JsonPointer from,
public JsonNode apply(final JsonNode node)
throws JsonPatchException
{
final JsonNode dupData = from.path(node).deepCopy();
final JsonNode dupData = node.at(from).deepCopy();
if (dupData.isMissingNode())
throw new JsonPatchException(BUNDLE.getMessage(
"jsonPatch.noSuchPath"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
package com.github.fge.jsonpatch;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.github.fge.jackson.jsonpointer.JsonPointer;

import java.io.IOException;

Expand Down
126 changes: 126 additions & 0 deletions src/main/java/com/github/fge/jsonpatch/JacksonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.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;


import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;

public final class JacksonUtils {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class contains methods that does not implement natively the Jackson JsonPointer class.


private final static JsonNodeFactory FACTORY = JsonNodeFactory.instance;
private final static int NO_SLASH = -1;
private final static ObjectReader READER;

static {
final ObjectMapper mapper = newMapper();
READER = mapper.reader();
}

private JacksonUtils()
{
}

public final static ObjectMapper newMapper() {
return new ObjectMapper().setNodeFactory(FACTORY)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN)
.enable(SerializationFeature.INDENT_OUTPUT);
}

/**
* Return a preconfigured {@link ObjectReader} to read JSON inputs
*
* @return the reader
* @see #newMapper()
*/
public static ObjectReader getReader()
{
return READER;
}

/**
* Return a preconfigured {@link JsonNodeFactory} to generate JSON data as
* {@link com.fasterxml.jackson.databind.JsonNode}s
*
* @return the factory
*/
public final static JsonNodeFactory nodeFactory()
{
return FACTORY;
}

public final static String getLast(JsonPointer jsonPointer)
{
String representation = jsonPointer.toString();

if(representation == null) return "";

int slashPosition = -1;
if((slashPosition = representation.lastIndexOf('/')) != -1)
{
return representation.substring(slashPosition+1);
}
else
{
return representation;
}


}

public final static JsonPointer empty()
{
return JsonPointer.compile("");
}

public final static JsonPointer head(JsonPointer jsonPointer)
{
String pointer = jsonPointer.toString();

int lastSlash = pointer.lastIndexOf('/');

if(lastSlash == NO_SLASH)
{
return empty();
}
else
{
return JsonPointer.compile(pointer.substring(0, lastSlash));
}
}

public final static JsonPointer append(JsonPointer pointer, String raw)
{
String p = pointer.toString();
return JsonPointer.compile(p + "/" + raw);
}

public final static JsonPointer append(JsonPointer pointer, int raw)
{
String p = pointer.toString();
return JsonPointer.compile(p + "/" + Integer.toString(raw));
}
}

132 changes: 132 additions & 0 deletions src/main/java/com/github/fge/jsonpatch/JsonNumEquals.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.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;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class JsonNumEquals
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Equivalence class but without dependency to Guava.

{

public static boolean equivalent(final JsonNode a, final JsonNode b)
{
/*
* If both are numbers, delegate to the helper method
*/
if (a.isNumber() && b.isNumber())
return numEquals(a, b);

final JsonNodeType typeA = a.getNodeType();
final JsonNodeType typeB = b.getNodeType();

/*
* If they are of different types, no dice
*/
if (typeA != typeB)
return false;

/*
* For all other primitive types than numbers, trust JsonNode
*/
if (!a.isContainerNode())
return a.equals(b);

/*
* OK, so they are containers (either both arrays or objects due to the
* test on types above). They are obviously not equal if they do not
* have the same number of elements/members.
*/
if (a.size() != b.size())
return false;

/*
* Delegate to the appropriate method according to their type.
*/
return typeA == JsonNodeType.ARRAY ? arrayEquals(a, b) : objectEquals(a, b);
}

private static boolean numEquals(final JsonNode a, final JsonNode b)
{
/*
* If both numbers are integers, delegate to JsonNode.
*/
if (a.isIntegralNumber() && b.isIntegralNumber())
return a.equals(b);

/*
* Otherwise, compare decimal values.
*/
return a.decimalValue().compareTo(b.decimalValue()) == 0;
}

private static boolean arrayEquals(final JsonNode a, final JsonNode b)
{
/*
* We are guaranteed here that arrays are the same size.
*/
final int size = a.size();

for (int i = 0; i < size; i++)
if (!equivalent(a.get(i), b.get(i)))
return false;

return true;
}

private static boolean objectEquals(final JsonNode a, final JsonNode b)
{
/*
* Grab the key set from the first node
*/
final Set<String> keys = newHashSet(a.fieldNames());

/*
* Grab the key set from the second node, and see if both sets are the
* same. If not, objects are not equal, no need to check for children.
*/
final Set<String> set = newHashSet(b.fieldNames());
if (!set.equals(keys))
return false;

/*
* Test each member individually.
*/
for (final String key: keys)
if (!equivalent(a.get(key), b.get(key)))
return false;

return true;
}

private static Set<String> newHashSet(Iterator<String> fields) {
final Set<String> elements = new HashSet<String>();

while(fields.hasNext()) {
elements.add(fields.next());
}

return elements;
}

}
Loading