Skip to content

Commit 0bf0b03

Browse files
authored
Merge pull request #8 from gravity9piotr/feature/787_json_path_parser
#787 JsonPath parsing
2 parents e340719 + 0379a5e commit 0bf0b03

5 files changed

Lines changed: 566 additions & 11 deletions

File tree

src/main/java/com/github/fge/jsonpatch/AddOperation.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,11 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
7575
if (path.isEmpty()) {
7676
return value;
7777
}
78-
/*
79-
* Check the parent node: it must exist and be a container (ie an array
80-
* or an object) for the add operation to work.
81-
*/
82-
final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path);
83-
final int lastDotIndex = fullJsonPath.lastIndexOf('.');
84-
final String newNodeName = fullJsonPath.substring(lastDotIndex + 1)
85-
.replace("[", "").replace("]", "");
86-
final String pathToParent = fullJsonPath.substring(0, lastDotIndex);
78+
PathDetails pathDetails = PathParser.getParentPathAndNewNodeName(path);
79+
final String pathToParent = pathDetails.getPathToParent();
80+
final String newNodeName = pathDetails.getNewNodeName();
8781

8882
final DocumentContext nodeContext = JsonPath.parse(node.deepCopy());
89-
9083
final JsonNode evaluatedJsonParents = nodeContext.read(pathToParent);
9184
if (evaluatedJsonParents == null) {
9285
throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchParent"));
@@ -95,7 +88,7 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
9588
throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer"));
9689
}
9790

98-
if (pathToParent.contains("[?(")) { // json filter result is always a list
91+
if (pathDetails.doesContainFiltersOrMultiIndexesNotation()) { // json filter result is always a list
9992
for (int i = 0; i < evaluatedJsonParents.size(); i++) {
10093
JsonNode parentNode = evaluatedJsonParents.get(i);
10194
if (!parentNode.isContainerNode()) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.github.fge.jsonpatch;
2+
3+
public class PathDetails {
4+
5+
private final String pathToParent;
6+
7+
private final String newNodeName;
8+
9+
private final boolean containsFiltersOrMultiIndexesNotation;
10+
11+
public PathDetails(String pathToParent, String newNodeName, boolean containsFiltersOrMultiIndexesNotation) {
12+
this.pathToParent = pathToParent;
13+
this.newNodeName = newNodeName;
14+
this.containsFiltersOrMultiIndexesNotation = containsFiltersOrMultiIndexesNotation;
15+
}
16+
17+
public String getPathToParent() {
18+
return pathToParent;
19+
}
20+
21+
public String getNewNodeName() {
22+
return newNodeName;
23+
}
24+
25+
public boolean doesContainFiltersOrMultiIndexesNotation() {
26+
return containsFiltersOrMultiIndexesNotation;
27+
}
28+
29+
@Override
30+
public String toString() {
31+
return "PathDetails{" +
32+
"pathToParent='" + pathToParent + '\'' +
33+
", newNodeName='" + newNodeName + '\'' +
34+
", containsFiltersOrMultiIndexesNotation=" + containsFiltersOrMultiIndexesNotation +
35+
'}';
36+
}
37+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.github.fge.jsonpatch;
2+
3+
import com.jayway.jsonpath.internal.Path;
4+
import com.jayway.jsonpath.internal.path.PathCompiler;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
public class PathParser {
10+
11+
private static final String FILTER_PLACEHOLDER = "[?]";
12+
13+
/**
14+
* This method parses JsonPath to find node name that needs to be added and path to the parent of new node.
15+
* Additionally, it finds if path contains filter or multi index notation (like [1:5])
16+
*
17+
* @param path Path in JsonPath or JsonPointer notation
18+
* @return PathDetails containing path to parent, name of new node and boolean value if path contains filter or multi
19+
* index notation
20+
* */
21+
public static PathDetails getParentPathAndNewNodeName(String path) throws JsonPatchException {
22+
final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path);
23+
final Path compiledPath = compilePath(fullJsonPath);
24+
String[] splitJsonPath = splitJsonPath(compiledPath);
25+
26+
int filterCounter = 0;
27+
List<String> filters = getFiltersOperations(fullJsonPath);
28+
boolean containsFiltersOrMultiIndexNotation = false;
29+
StringBuilder sb = new StringBuilder();
30+
sb.append("$");
31+
for (int i = 0; i < splitJsonPath.length - 1; i++) {
32+
if (splitJsonPath[i].isEmpty()) {
33+
continue;
34+
}
35+
if (splitJsonPath[i].equals(FILTER_PLACEHOLDER)) {
36+
sb.append(filters.get(filterCounter++));
37+
containsFiltersOrMultiIndexNotation = true;
38+
} else if (isArrayPart(splitJsonPath[i])) {
39+
sb.append(splitJsonPath[i]);
40+
if (isMultiIndexNotation(splitJsonPath[i])) {
41+
containsFiltersOrMultiIndexNotation = true;
42+
}
43+
} else if (isDoubleDot(splitJsonPath[i])) {
44+
sb.append(splitJsonPath[i]);
45+
} else {
46+
sb.append("[").append(splitJsonPath[i]).append("]");
47+
}
48+
}
49+
final String pathToParent = sb.toString();
50+
final String newNodeName = getNewNodeName(splitJsonPath);
51+
return new PathDetails(pathToParent, newNodeName, containsFiltersOrMultiIndexNotation);
52+
}
53+
54+
private static boolean isMultiIndexNotation(String path) {
55+
String pathWithoutBracket = path
56+
.replace("[", "")
57+
.replace("]", "");
58+
return !pathWithoutBracket.startsWith("'") && !pathWithoutBracket.matches("[0-9]+");
59+
}
60+
61+
private static String getNewNodeName(String[] splitJsonPath) {
62+
return splitJsonPath[splitJsonPath.length - 1]
63+
.replace("'", "")
64+
.replace("[", "")
65+
.replace("]", "");
66+
}
67+
68+
/**
69+
* Removes $ sign from the beginning of the path for easier processing - it will be added later on
70+
* This method is called after PathCompiler.compile, so we are sure now, that the path is correct
71+
* and bracket notation is used.
72+
* We can now split JsonPath using positive lookahead regex (without removing separator).
73+
* */
74+
private static String[] splitJsonPath(Path compiledPath) {
75+
return compiledPath.toString()
76+
.replace("$", "")
77+
.split("(?=\\[)");
78+
}
79+
80+
private static Path compilePath(String fullJsonPath) throws JsonPatchException {
81+
try {
82+
return PathCompiler.compile(fullJsonPath);
83+
} catch (Exception e) {
84+
throw new JsonPatchException("Non-compilable path provided");
85+
}
86+
}
87+
88+
private static boolean isDoubleDot(String jsonPathPart) {
89+
return jsonPathPart.equals("..");
90+
}
91+
92+
private static boolean isArrayPart(String jsonPathPart) {
93+
return jsonPathPart.startsWith("[") && jsonPathPart.endsWith("]");
94+
}
95+
96+
private static List<String> getFiltersOperations(String jsonPath) {
97+
if (!jsonPath.contains("[?(")) {
98+
return new ArrayList<>();
99+
}
100+
List<String> filters = new ArrayList<>();
101+
int openingBracketPosition = -1;
102+
int counter = 0;
103+
for (int i = 0; i < jsonPath.length(); i++) {
104+
if (jsonPath.charAt(i) == '[' && jsonPath.charAt(i+1) == '?') {
105+
if (openingBracketPosition == -1) {
106+
openingBracketPosition = i;
107+
}
108+
counter++;
109+
}
110+
if (jsonPath.charAt(i) == ']' && counter > 0) {
111+
counter--;
112+
if (counter == 0) {
113+
filters.add(jsonPath.substring(openingBracketPosition, i+1));
114+
openingBracketPosition = -1;
115+
}
116+
}
117+
}
118+
return filters;
119+
}
120+
}

0 commit comments

Comments
 (0)