Skip to content

Commit 73a9438

Browse files
committed
Merge branch 'issue94' of https://github.com/holograph/zjsonpatch into holograph-issue94
2 parents 019a039 + 39b8637 commit 73a9438

25 files changed

+893
-400
lines changed

src/main/java/com/flipkart/zjsonpatch/Diff.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,33 @@
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
2020

21-
import java.util.List;
22-
2321
/**
2422
* User: gopi.vishwakarma
2523
* Date: 30/07/14
2624
*/
2725
class Diff {
2826
private final Operation operation;
29-
private final List<Object> path;
27+
private final JsonPointer path;
3028
private final JsonNode value;
31-
private List<Object> toPath; //only to be used in move operation
29+
private JsonPointer toPath; //only to be used in move operation
3230
private final JsonNode srcValue; // only used in replace operation
3331

34-
Diff(Operation operation, List<Object> path, JsonNode value) {
32+
Diff(Operation operation, JsonPointer path, JsonNode value) {
3533
this.operation = operation;
3634
this.path = path;
3735
this.value = value;
3836
this.srcValue = null;
3937
}
4038

41-
Diff(Operation operation, List<Object> fromPath, List<Object> toPath) {
39+
Diff(Operation operation, JsonPointer fromPath, JsonPointer toPath) {
4240
this.operation = operation;
4341
this.path = fromPath;
4442
this.toPath = toPath;
4543
this.value = null;
4644
this.srcValue = null;
4745
}
4846

49-
Diff(Operation operation, List<Object> path, JsonNode srcValue, JsonNode value) {
47+
Diff(Operation operation, JsonPointer path, JsonNode srcValue, JsonNode value) {
5048
this.operation = operation;
5149
this.path = path;
5250
this.value = value;
@@ -57,23 +55,23 @@ public Operation getOperation() {
5755
return operation;
5856
}
5957

60-
public List<Object> getPath() {
58+
public JsonPointer getPath() {
6159
return path;
6260
}
6361

6462
public JsonNode getValue() {
6563
return value;
6664
}
6765

68-
public static Diff generateDiff(Operation replace, List<Object> path, JsonNode target) {
66+
public static Diff generateDiff(Operation replace, JsonPointer path, JsonNode target) {
6967
return new Diff(replace, path, target);
7068
}
7169

72-
public static Diff generateDiff(Operation replace, List<Object> path, JsonNode source, JsonNode target) {
70+
public static Diff generateDiff(Operation replace, JsonPointer path, JsonNode source, JsonNode target) {
7371
return new Diff(replace, path, source, target);
7472
}
75-
76-
List<Object> getToPath() {
73+
74+
JsonPointer getToPath() {
7775
return toPath;
7876
}
7977

src/main/java/com/flipkart/zjsonpatch/InPlaceApplyProcessor.java

Lines changed: 84 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import com.fasterxml.jackson.databind.node.ObjectNode;
2222

2323
import java.util.EnumSet;
24-
import java.util.List;
2524

2625
class InPlaceApplyProcessor implements JsonPatchProcessor {
2726

@@ -42,187 +41,123 @@ public JsonNode result() {
4241
}
4342

4443
@Override
45-
public void move(List<String> fromPath, List<String> toPath) {
46-
JsonNode parentNode = getParentNode(fromPath, Operation.MOVE);
47-
String field = fromPath.get(fromPath.size() - 1).replaceAll("\"", "");
48-
JsonNode valueNode = parentNode.isArray() ? parentNode.get(Integer.parseInt(field)) : parentNode.get(field);
44+
public void move(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException {
45+
JsonNode valueNode = fromPath.evaluate(target);
4946
remove(fromPath);
50-
add(toPath, valueNode);
47+
set(toPath, valueNode, Operation.MOVE);
5148
}
5249

5350
@Override
54-
public void copy(List<String> fromPath, List<String> toPath) {
55-
JsonNode parentNode = getParentNode(fromPath, Operation.COPY);
56-
String field = fromPath.get(fromPath.size() - 1).replaceAll("\"", "");
57-
JsonNode valueNode = parentNode.isArray() ? parentNode.get(Integer.parseInt(field)) : parentNode.get(field);
51+
public void copy(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException {
52+
JsonNode valueNode = fromPath.evaluate(target);
5853
JsonNode valueToCopy = valueNode != null ? valueNode.deepCopy() : null;
59-
add(toPath, valueToCopy);
54+
set(toPath, valueToCopy, Operation.COPY);
6055
}
6156

62-
@Override
63-
public void test(List<String> path, JsonNode value) {
64-
if (path.isEmpty()) {
65-
error(Operation.TEST, "path is empty , path : ");
66-
} else {
67-
JsonNode parentNode = getParentNode(path, Operation.TEST);
68-
String fieldToReplace = path.get(path.size() - 1).replaceAll("\"", "");
69-
if (fieldToReplace.equals("") && path.size() == 1)
70-
if (target.equals(value)) {
71-
target = value;
72-
} else {
73-
error(Operation.TEST, "value mismatch");
74-
}
75-
else if (!parentNode.isContainerNode())
76-
error(Operation.TEST, "parent is not a container in source, path provided : " + PathUtils.getPathRepresentation(path) + " | node : " + parentNode);
77-
else if (parentNode.isArray()) {
78-
final ArrayNode target = (ArrayNode) parentNode;
79-
String idxStr = path.get(path.size() - 1);
80-
81-
if ("-".equals(idxStr)) {
82-
// see http://tools.ietf.org/html/rfc6902#section-4.1
83-
if (!target.get(target.size() - 1).equals(value)) {
84-
error(Operation.TEST, "value mismatch");
85-
}
86-
} else {
87-
int idx = arrayIndex(idxStr.replaceAll("\"", ""), target.size(), false);
88-
if (!target.get(idx).equals(value)) {
89-
error(Operation.TEST, "value mismatch");
90-
}
91-
}
92-
} else {
93-
final ObjectNode target = (ObjectNode) parentNode;
94-
String key = path.get(path.size() - 1).replaceAll("\"", "");
95-
JsonNode actual = target.get(key);
96-
if (actual == null)
97-
error(Operation.TEST, "noSuchPath in source, path provided : " + PathUtils.getPathRepresentation(path));
98-
else if (!actual.equals(value))
99-
error(Operation.TEST, "value mismatch");
100-
}
101-
}
57+
private static String show(JsonNode value) {
58+
if (value == null || value.isNull())
59+
return "null";
60+
else if (value.isArray())
61+
return "array";
62+
else if (value.isObject())
63+
return "object";
64+
else
65+
return "value " + value.toString(); // Caveat: numeric may differ from source (e.g. trailing zeros)
10266
}
10367

10468
@Override
105-
public void add(List<String> path, JsonNode value) {
106-
if (path.isEmpty()) {
107-
error(Operation.ADD, "path is empty , path : ");
108-
} else {
109-
JsonNode parentNode = getParentNode(path, Operation.ADD);
110-
String fieldToReplace = path.get(path.size() - 1).replaceAll("\"", "");
111-
if (fieldToReplace.equals("") && path.size() == 1)
112-
target = value;
113-
else if (!parentNode.isContainerNode())
114-
error(Operation.ADD, "parent is not a container in source, path provided : " + PathUtils.getPathRepresentation(path) + " | node : " + parentNode);
115-
else if (parentNode.isArray())
116-
addToArray(path, value, parentNode);
117-
else
118-
addToObject(path, parentNode, value);
119-
}
69+
public void test(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
70+
JsonNode valueNode = path.evaluate(target);
71+
if (!valueNode.equals(value))
72+
throw new JsonPatchApplicationException(
73+
"Expected " + show(value) + " but found " + show(valueNode), Operation.TEST, path);
12074
}
12175

122-
private void addToObject(List<String> path, JsonNode node, JsonNode value) {
123-
final ObjectNode target = (ObjectNode) node;
124-
String key = path.get(path.size() - 1).replaceAll("\"", "");
125-
target.set(key, value);
76+
@Override
77+
public void add(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
78+
set(path, value, Operation.ADD);
12679
}
12780

128-
private void addToArray(List<String> path, JsonNode value, JsonNode parentNode) {
129-
final ArrayNode target = (ArrayNode) parentNode;
130-
String idxStr = path.get(path.size() - 1);
81+
@Override
82+
public void replace(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
83+
if (path.isRoot()) {
84+
target = value;
85+
return;
86+
}
13187

132-
if ("-".equals(idxStr)) {
133-
// see http://tools.ietf.org/html/rfc6902#section-4.1
134-
target.add(value);
88+
JsonNode parentNode = path.getParent().evaluate(target);
89+
JsonPointer.RefToken token = path.last();
90+
if (parentNode.isObject()) {
91+
if (!parentNode.has(token.getField()))
92+
throw new JsonPatchApplicationException(
93+
"Missing field \"" + token.getField() + "\"", Operation.REPLACE, path.getParent());
94+
((ObjectNode) parentNode).replace(token.getField(), value);
95+
} else if (parentNode.isArray()) {
96+
if (token.getIndex() >= parentNode.size())
97+
throw new JsonPatchApplicationException(
98+
"Array index " + token.getIndex() + " out of bounds", Operation.REPLACE, path.getParent());
99+
((ArrayNode) parentNode).set(token.getIndex(), value);
135100
} else {
136-
int idx = arrayIndex(idxStr.replaceAll("\"", ""), target.size(), false);
137-
target.insert(idx, value);
101+
throw new JsonPatchApplicationException(
102+
"Can't reference past scalar value", Operation.REPLACE, path.getParent());
138103
}
139104
}
140105

141106
@Override
142-
public void replace(List<String> path, JsonNode value) {
143-
if (path.isEmpty()) {
144-
error(Operation.REPLACE, "path is empty");
107+
public void remove(JsonPointer path) throws JsonPointerEvaluationException {
108+
if (path.isRoot())
109+
throw new JsonPatchApplicationException("Cannot remove document root", Operation.REMOVE, path);
110+
111+
JsonNode parentNode = path.getParent().evaluate(target);
112+
JsonPointer.RefToken token = path.last();
113+
if (parentNode.isObject())
114+
((ObjectNode) parentNode).remove(token.getField());
115+
else if (parentNode.isArray()) {
116+
if (!flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT) &&
117+
token.getIndex() >= parentNode.size())
118+
throw new JsonPatchApplicationException(
119+
"Array index " + token.getIndex() + " out of bounds", Operation.REPLACE, path.getParent());
120+
((ArrayNode) parentNode).remove(token.getIndex());
145121
} else {
146-
JsonNode parentNode = getParentNode(path, Operation.REPLACE);
147-
String fieldToReplace = path.get(path.size() - 1).replaceAll("\"", "");
148-
if (isNullOrEmpty(fieldToReplace) && path.size() == 1)
149-
target = value;
150-
else if (parentNode.isObject() && parentNode.has(fieldToReplace)) {
151-
((ObjectNode) parentNode).replace(fieldToReplace, value);
152-
} else if (parentNode.isArray())
153-
((ArrayNode) parentNode).set(arrayIndex(fieldToReplace, parentNode.size() - 1, false), value);
154-
else
155-
error(Operation.REPLACE, "noSuchPath in source, path provided : " + PathUtils.getPathRepresentation(path));
122+
throw new JsonPatchApplicationException(
123+
"Cannot reference past scalar value", Operation.REPLACE, path.getParent());
156124
}
157125
}
158126

159-
@Override
160-
public void remove(List<String> path) {
161-
if (path.isEmpty()) {
162-
error(Operation.REMOVE, "path is empty");
163-
} else {
164-
JsonNode parentNode = getParentNode(path, Operation.REMOVE);
165-
String fieldToRemove = path.get(path.size() - 1).replaceAll("\"", "");
166-
if (parentNode.isObject())
167-
((ObjectNode) parentNode).remove(fieldToRemove);
127+
128+
129+
private void set(JsonPointer path, JsonNode value, Operation forOp) throws JsonPointerEvaluationException {
130+
if (path.isRoot())
131+
target = value;
132+
else {
133+
JsonNode parentNode = path.getParent().evaluate(target);
134+
if (!parentNode.isContainerNode())
135+
throw new JsonPatchApplicationException("Cannot reference past scalar value", forOp, path.getParent());
168136
else if (parentNode.isArray())
169-
((ArrayNode) parentNode).remove(arrayIndex(fieldToRemove, parentNode.size() - 1, flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT)));
137+
addToArray(path, value, parentNode);
170138
else
171-
error(Operation.REMOVE, "noSuchPath in source, path provided : " + PathUtils.getPathRepresentation(path));
139+
addToObject(path, parentNode, value);
172140
}
173141
}
174142

175-
private void error(Operation forOp, String message) {
176-
throw new JsonPatchApplicationException("[" + forOp + " Operation] " + message);
143+
private void addToObject(JsonPointer path, JsonNode node, JsonNode value) {
144+
final ObjectNode target = (ObjectNode) node;
145+
String key = path.last().getField();
146+
target.set(key, value);
177147
}
178148

179-
private JsonNode getParentNode(List<String> fromPath, Operation forOp) {
180-
List<String> pathToParent = fromPath.subList(0, fromPath.size() - 1); // would never by out of bound, lets see
181-
JsonNode node = getNode(target, pathToParent, 1);
182-
if (node == null)
183-
error(forOp, "noSuchPath in source, path provided: " + PathUtils.getPathRepresentation(fromPath));
184-
return node;
185-
}
149+
private void addToArray(JsonPointer path, JsonNode value, JsonNode parentNode) {
150+
final ArrayNode target = (ArrayNode) parentNode;
151+
int idx = path.last().getIndex();
186152

187-
private JsonNode getNode(JsonNode ret, List<String> path, int pos) {
188-
if (pos >= path.size()) {
189-
return ret;
190-
}
191-
String key = path.get(pos);
192-
if (ret.isArray()) {
193-
int keyInt = Integer.parseInt(key.replaceAll("\"", ""));
194-
JsonNode element = ret.get(keyInt);
195-
if (element == null)
196-
return null;
197-
else
198-
return getNode(ret.get(keyInt), path, ++pos);
199-
} else if (ret.isObject()) {
200-
if (ret.has(key)) {
201-
return getNode(ret.get(key), path, ++pos);
202-
}
203-
return null;
153+
if (idx == JsonPointer.LAST_INDEX) {
154+
// see http://tools.ietf.org/html/rfc6902#section-4.1
155+
target.add(value);
204156
} else {
205-
return ret;
206-
}
207-
}
208-
209-
private int arrayIndex(String s, int max, boolean allowNoneExisting) {
210-
int index;
211-
try {
212-
index = Integer.parseInt(s);
213-
} catch (NumberFormatException nfe) {
214-
throw new JsonPatchApplicationException("Object operation on array target");
215-
}
216-
if (index < 0) {
217-
throw new JsonPatchApplicationException("index Out of bound, index is negative");
218-
} else if (index > max) {
219-
if (!allowNoneExisting)
220-
throw new JsonPatchApplicationException("index Out of bound, index is greater than " + max);
157+
if (idx > target.size())
158+
throw new JsonPatchApplicationException(
159+
"Array index " + idx + " out of bounds", Operation.ADD, path.getParent());
160+
target.insert(idx, value);
221161
}
222-
return index;
223-
}
224-
225-
private boolean isNullOrEmpty(String string) {
226-
return string == null || string.length() == 0;
227162
}
228163
}

src/main/java/com/flipkart/zjsonpatch/InvalidJsonPatchException.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@
2222
*/
2323
public class InvalidJsonPatchException extends JsonPatchApplicationException {
2424
public InvalidJsonPatchException(String message) {
25-
super(message);
26-
}
27-
28-
public InvalidJsonPatchException(String message, Throwable cause) {
29-
super(message, cause);
30-
}
31-
32-
public InvalidJsonPatchException(Throwable cause) {
33-
super(cause);
25+
super(message, null, null);
3426
}
3527
}

0 commit comments

Comments
 (0)