T notEmpty(T chars, String message) {
+ if (chars == null || chars.length() == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ return chars;
+ }
+
+ /**
+ * Validate that the specified argument character sequence is
+ * neither {@code null} nor a length of zero (no characters);
+ * otherwise throwing an exception with the specified message.
+ *
+ * Validate.notEmpty(myString, "The string must not be empty");
+ *
+ * @param bytes the bytes to check, validated not null by this method
+ * @param message the {@link String#format(String, Object...)} exception message if invalid, not null
+ * @return the validated character sequence (never {@code null} method for chaining)
+ * @throws NullPointerException if the character sequence is {@code null}
+ * @throws IllegalArgumentException if the character sequence is empty
+ */
+ public static byte[] notEmpty(byte[] bytes, String message) {
+ if (bytes == null || bytes.length == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ return bytes;
+ }
+
/**
* Validate that the specified argument character sequence is
* neither {@code null} nor a length of zero (no characters);
@@ -379,10 +439,7 @@ public static boolean onlyOneIsTrueNonThrow(final boolean... expressions) {
* @throws IllegalArgumentException if the character sequence is empty
*/
public static T notEmpty(T chars, String message, Object... values) {
- if (chars == null) {
- throw new IllegalArgumentException(String.format(message, values));
- }
- if (chars.length() == 0) {
+ if (chars == null || chars.length() == 0) {
throw new IllegalArgumentException(String.format(message, values));
}
return chars;
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
old mode 100644
new mode 100755
index ac47274a7..322a17198
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/EvaluatorFactory.java
@@ -4,7 +4,12 @@
import com.jayway.jsonpath.Predicate;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.regex.Pattern;
+
+import static com.jayway.jsonpath.internal.filter.ValueNodes.PatternNode;
+import static com.jayway.jsonpath.internal.filter.ValueNodes.ValueListNode;
public class EvaluatorFactory {
@@ -30,6 +35,8 @@ public class EvaluatorFactory {
evaluators.put(RelationalOperator.MATCHES, new PredicateMatchEvaluator());
evaluators.put(RelationalOperator.TYPE, new TypeEvaluator());
evaluators.put(RelationalOperator.SUBSETOF, new SubsetOfEvaluator());
+ evaluators.put(RelationalOperator.ANYOF, new AnyOfEvaluator());
+ evaluators.put(RelationalOperator.NONEOF, new NoneOfEvaluator());
}
public static Evaluator createEvaluator(RelationalOperator operator){
@@ -95,6 +102,8 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) < 0;
} if(left.isStringNode() && right.isStringNode()){
return left.asStringNode().getString().compareTo(right.asStringNode().getString()) < 0;
+ } if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) < 0;
}
return false;
}
@@ -107,6 +116,8 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) <= 0;
} if(left.isStringNode() && right.isStringNode()){
return left.asStringNode().getString().compareTo(right.asStringNode().getString()) <= 0;
+ } if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) <= 0;
}
return false;
}
@@ -119,6 +130,8 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) > 0;
} else if(left.isStringNode() && right.isStringNode()){
return left.asStringNode().getString().compareTo(right.asStringNode().getString()) > 0;
+ } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) > 0;
}
return false;
}
@@ -131,6 +144,8 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
return left.asNumberNode().getNumber().compareTo(right.asNumberNode().getNumber()) >= 0;
} else if(left.isStringNode() && right.isStringNode()){
return left.asStringNode().getString().compareTo(right.asStringNode().getString()) >= 0;
+ } else if (left.isOffsetDateTimeNode() && right.isOffsetDateTimeNode()){ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ return left.asOffsetDateTimeNode().getDate().compareTo(right.asOffsetDateTimeNode().getDate()) >= 0;
}
return false;
}
@@ -168,7 +183,7 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
private static class InEvaluator implements Evaluator {
@Override
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
- ValueNode.ValueListNode valueListNode;
+ ValueListNode valueListNode;
if(right.isJsonNode()){
ValueNode vn = right.asJsonNode().asValueListNode(ctx);
if(vn.isUndefinedNode()){
@@ -193,12 +208,12 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
private static class AllEvaluator implements Evaluator {
@Override
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
- ValueNode.ValueListNode requiredValues = right.asValueListNode();
+ ValueListNode requiredValues = right.asValueListNode();
if(left.isJsonNode()){
ValueNode valueNode = left.asJsonNode().asValueListNode(ctx); //returns UndefinedNode if conversion is not possible
if(valueNode.isValueListNode()){
- ValueNode.ValueListNode shouldContainAll = valueNode.asValueListNode();
+ ValueListNode shouldContainAll = valueNode.asValueListNode();
for (ValueNode required : requiredValues) {
if(!shouldContainAll.contains(required)){
return false;
@@ -243,16 +258,41 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
}
if (left.isPatternNode()) {
- return matches(left.asPatternNode(), getInput(right));
+ if (right.isValueListNode() || (right.isJsonNode() && right.asJsonNode().isArray(ctx))) {
+ return matchesAny(left.asPatternNode(), right.asJsonNode().asValueListNode(ctx));
+ } else {
+ return matches(left.asPatternNode(), getInput(right));
+ }
} else {
- return matches(right.asPatternNode(), getInput(left));
+ if (left.isValueListNode() || (left.isJsonNode() && left.asJsonNode().isArray(ctx))) {
+ return matchesAny(right.asPatternNode(), left.asJsonNode().asValueListNode(ctx));
+ } else {
+ return matches(right.asPatternNode(), getInput(left));
+ }
}
}
- private boolean matches(ValueNode.PatternNode patternNode, String inputToMatch) {
+ private boolean matches(PatternNode patternNode, String inputToMatch) {
return patternNode.getCompiledPattern().matcher(inputToMatch).matches();
}
+ private boolean matchesAny(PatternNode patternNode, ValueNode valueNode) {
+ if (!valueNode.isValueListNode()) {
+ return false;
+ }
+
+ ValueListNode listNode = valueNode.asValueListNode();
+ Pattern pattern = patternNode.getCompiledPattern();
+
+ for (Iterator it = listNode.iterator(); it.hasNext(); ) {
+ String input = getInput(it.next());
+ if (pattern.matcher(input).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private String getInput(ValueNode valueNode) {
String input = "";
@@ -269,7 +309,7 @@ private String getInput(ValueNode valueNode) {
private static class SubsetOfEvaluator implements Evaluator {
@Override
public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
- ValueNode.ValueListNode rightValueListNode;
+ ValueListNode rightValueListNode;
if(right.isJsonNode()){
ValueNode vn = right.asJsonNode().asValueListNode(ctx);
if(vn.isUndefinedNode()){
@@ -280,7 +320,7 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
} else {
rightValueListNode = right.asValueListNode();
}
- ValueNode.ValueListNode leftValueListNode;
+ ValueListNode leftValueListNode;
if(left.isJsonNode()){
ValueNode vn = left.asJsonNode().asValueListNode(ctx);
if(vn.isUndefinedNode()){
@@ -295,4 +335,77 @@ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateCont
}
}
+ private static class AnyOfEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ ValueListNode rightValueListNode;
+ if (right.isJsonNode()) {
+ ValueNode vn = right.asJsonNode().asValueListNode(ctx);
+ if (vn.isUndefinedNode()) {
+ return false;
+ } else {
+ rightValueListNode = vn.asValueListNode();
+ }
+ } else {
+ rightValueListNode = right.asValueListNode();
+ }
+ ValueListNode leftValueListNode;
+ if (left.isJsonNode()) {
+ ValueNode vn = left.asJsonNode().asValueListNode(ctx);
+ if (vn.isUndefinedNode()) {
+ return false;
+ } else {
+ leftValueListNode = vn.asValueListNode();
+ }
+ } else {
+ leftValueListNode = left.asValueListNode();
+ }
+
+ for (ValueNode leftValueNode : leftValueListNode) {
+ for (ValueNode rightValueNode : rightValueListNode) {
+ if (leftValueNode.equals(rightValueNode)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class NoneOfEvaluator implements Evaluator {
+ @Override
+ public boolean evaluate(ValueNode left, ValueNode right, Predicate.PredicateContext ctx) {
+ ValueListNode rightValueListNode;
+ if (right.isJsonNode()) {
+ ValueNode vn = right.asJsonNode().asValueListNode(ctx);
+ if (vn.isUndefinedNode()) {
+ return false;
+ } else {
+ rightValueListNode = vn.asValueListNode();
+ }
+ } else {
+ rightValueListNode = right.asValueListNode();
+ }
+ ValueListNode leftValueListNode;
+ if (left.isJsonNode()) {
+ ValueNode vn = left.asJsonNode().asValueListNode(ctx);
+ if (vn.isUndefinedNode()) {
+ return false;
+ } else {
+ leftValueListNode = vn.asValueListNode();
+ }
+ } else {
+ leftValueListNode = left.asValueListNode();
+ }
+
+ for (ValueNode leftValueNode : leftValueListNode) {
+ for (ValueNode rightValueNode : rightValueListNode) {
+ if (leftValueNode.equals(rightValueNode)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
index 67b512e41..ecec37091 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
@@ -4,13 +4,14 @@
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.CharacterIndex;
+import static com.jayway.jsonpath.internal.filter.ValueNodes.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
-public class FilterCompiler {
+public class FilterCompiler {
private static final Logger logger = LoggerFactory.getLogger(FilterCompiler.class);
private static final char DOC_CONTEXT = '$';
@@ -132,11 +133,9 @@ private ExpressionNode readLogicalOR() {
while (true) {
int savepoint = filter.position();
- try {
- filter.readSignificantSubSequence(LogicalOperator.OR.getOperatorString());
+ if (filter.hasSignificantSubSequence(LogicalOperator.OR.getOperatorString())) {
ops.add(readLogicalAND());
- }
- catch (InvalidPathException exc) {
+ } else {
filter.setPosition(savepoint);
break;
}
@@ -152,11 +151,9 @@ private ExpressionNode readLogicalAND() {
while (true) {
int savepoint = filter.position();
- try {
- filter.readSignificantSubSequence(LogicalOperator.AND.getOperatorString());
+ if (filter.hasSignificantSubSequence(LogicalOperator.AND.getOperatorString())) {
ops.add(readLogicalANDOperand());
- }
- catch (InvalidPathException exc) {
+ } else {
filter.setPosition(savepoint);
break;
}
@@ -201,10 +198,10 @@ private RelationalExpressionNode readExpression() {
filter.setPosition(savepoint);
}
- ValueNode.PathNode pathNode = left.asPathNode();
+ PathNode pathNode = left.asPathNode();
left = pathNode.asExistsCheck(pathNode.shouldExists());
RelationalOperator operator = RelationalOperator.EXISTS;
- ValueNode right = left.asPathNode().shouldExists() ? ValueNode.TRUE : ValueNode.FALSE;
+ ValueNode right = left.asPathNode().shouldExists() ? ValueNodes.TRUE : ValueNodes.FALSE;
return new RelationalExpressionNode(left, operator, right);
}
@@ -243,7 +240,7 @@ private RelationalOperator readRelationalOperator() {
return RelationalOperator.fromString(operator.toString());
}
- private ValueNode.NullNode readNullLiteral() {
+ private NullNode readNullLiteral() {
int begin = filter.position();
if(filter.currentChar() == NULL && filter.inBounds(filter.position() + 3)){
CharSequence nullValue = filter.subSequence(filter.position(), filter.position() + 4);
@@ -256,7 +253,7 @@ private ValueNode.NullNode readNullLiteral() {
throw new InvalidPathException("Expected value");
}
- private ValueNode.JsonNode readJsonLiteral(){
+ private JsonNode readJsonLiteral(){
int begin = filter.position();
char openChar = filter.currentChar();
@@ -277,14 +274,32 @@ private ValueNode.JsonNode readJsonLiteral(){
}
- private ValueNode.PatternNode readPattern() {
+ private int endOfFlags(int position) {
+ int endIndex = position;
+ char[] currentChar = new char[1];
+ while (filter.inBounds(endIndex)) {
+ currentChar[0] = filter.charAt(endIndex);
+ if (PatternFlag.parseFlags(currentChar) > 0) {
+ endIndex++;
+ continue;
+ }
+ break;
+ }
+ return endIndex;
+ }
+
+ private PatternNode readPattern() {
int begin = filter.position();
int closingIndex = filter.nextIndexOfUnescaped(PATTERN);
if (closingIndex == -1) {
throw new InvalidPathException("Pattern not closed. Expected " + PATTERN + " in " + filter);
} else {
- if(filter.inBounds(closingIndex+1) && filter.charAt(closingIndex+1) == IGNORE_CASE){
- closingIndex++;
+ if (filter.inBounds(closingIndex+1)) {
+ int endFlagsIndex = endOfFlags(closingIndex + 1);
+ if (endFlagsIndex > closingIndex) {
+ CharSequence flags = filter.subSequence(closingIndex + 1, endFlagsIndex);
+ closingIndex += flags.length();
+ }
}
filter.setPosition(closingIndex + 1);
}
@@ -293,7 +308,7 @@ private ValueNode.PatternNode readPattern() {
return ValueNode.createPatternNode(pattern);
}
- private ValueNode.StringNode readStringLiteral(char endChar) {
+ private StringNode readStringLiteral(char endChar) {
int begin = filter.position();
int closingSingleQuoteIndex = filter.nextIndexOfUnescaped(endChar);
@@ -307,7 +322,7 @@ private ValueNode.StringNode readStringLiteral(char endChar) {
return ValueNode.createStringNode(stringLiteral, true);
}
- private ValueNode.NumberNode readNumberLiteral() {
+ private NumberNode readNumberLiteral() {
int begin = filter.position();
while (filter.inBounds() && filter.isNumberCharacter(filter.position())) {
@@ -318,7 +333,7 @@ private ValueNode.NumberNode readNumberLiteral() {
return ValueNode.createNumberNode(numberLiteral);
}
- private ValueNode.BooleanNode readBooleanLiteral() {
+ private BooleanNode readBooleanLiteral() {
int begin = filter.position();
int end = filter.currentChar() == TRUE ? filter.position() + 3 : filter.position() + 4;
@@ -335,7 +350,7 @@ private ValueNode.BooleanNode readBooleanLiteral() {
return ValueNode.createBooleanNode(boolValue);
}
- private ValueNode.PathNode readPath() {
+ private PathNode readPath() {
char previousSignificantChar = filter.previousSignificantChar();
int begin = filter.position();
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java
new file mode 100644
index 000000000..3ac051288
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java
@@ -0,0 +1,48 @@
+package com.jayway.jsonpath.internal.filter;
+
+import java.util.regex.Pattern;
+
+public enum PatternFlag {
+ UNIX_LINES(Pattern.UNIX_LINES, 'd'),
+ CASE_INSENSITIVE(Pattern.CASE_INSENSITIVE, 'i'),
+ COMMENTS(Pattern.COMMENTS, 'x'),
+ MULTILINE(Pattern.MULTILINE, 'm'),
+ DOTALL(Pattern.DOTALL, 's'),
+ UNICODE_CASE(Pattern.UNICODE_CASE, 'u'),
+ UNICODE_CHARACTER_CLASS(Pattern.UNICODE_CHARACTER_CLASS, 'U');
+
+ private final int code;
+ private final char flag;
+
+ private PatternFlag(int code, char flag) {
+ this.code = code;
+ this.flag = flag;
+ }
+
+ public static int parseFlags(char[] flags) {
+ int flagsValue = 0;
+ for (char flag : flags) {
+ flagsValue |= getCodeByFlag(flag);
+ }
+ return flagsValue;
+ }
+
+ public static String parseFlags(int flags) {
+ StringBuilder builder = new StringBuilder();
+ for (PatternFlag patternFlag : PatternFlag.values()) {
+ if ((patternFlag.code & flags) == patternFlag.code) {
+ builder.append(patternFlag.flag);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static int getCodeByFlag(char flag) {
+ for (PatternFlag patternFlag : PatternFlag.values()) {
+ if (patternFlag.flag == flag) {
+ return patternFlag.code;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
index 830cc3bb3..509869450 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/RelationalOperator.java
@@ -2,6 +2,8 @@
import com.jayway.jsonpath.InvalidPathException;
+import java.util.Locale;
+
public enum RelationalOperator {
GTE(">="),
@@ -30,7 +32,9 @@ public enum RelationalOperator {
TYPE("TYPE"),
MATCHES("MATCHES"),
EMPTY("EMPTY"),
- SUBSETOF("SUBSETOF");
+ SUBSETOF("SUBSETOF"),
+ ANYOF("ANYOF"),
+ NONEOF("NONEOF");
private final String operatorString;
@@ -38,9 +42,10 @@ public enum RelationalOperator {
this.operatorString = operatorString;
}
- public static RelationalOperator fromString(String operatorString){
+ public static RelationalOperator fromString(String operatorString) {
+ String upperCaseOperatorString = operatorString.toUpperCase(Locale.ROOT);
for (RelationalOperator operator : RelationalOperator.values()) {
- if(operator.operatorString.equals(operatorString.toUpperCase()) ){
+ if(operator.operatorString.equals(upperCaseOperatorString) ){
return operator;
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
index 5135f848a..cdf0dd97f 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
@@ -1,39 +1,18 @@
package com.jayway.jsonpath.internal.filter;
-import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPathException;
-import com.jayway.jsonpath.Option;
-import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.Path;
-import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.path.PathCompiler;
-import com.jayway.jsonpath.internal.path.PredicateContextImpl;
-import com.jayway.jsonpath.spi.json.JsonProvider;
-
import net.minidev.json.parser.JSONParser;
-import net.minidev.json.parser.ParseException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-public abstract class ValueNode {
+import java.time.OffsetDateTime;
+import java.util.regex.Pattern;
- public static final NullNode NULL_NODE = new NullNode();
- public static final BooleanNode TRUE = new BooleanNode("true");
- public static final BooleanNode FALSE = new BooleanNode("false");
- public static final UndefinedNode UNDEFINED = new UndefinedNode();
+import static com.jayway.jsonpath.internal.filter.ValueNodes.*;
+public abstract class ValueNode {
public abstract Class> type(Predicate.PredicateContext ctx);
@@ -125,6 +104,16 @@ public ClassNode asClassNode() {
throw new InvalidPathException("Expected class node");
}
+ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ public boolean isOffsetDateTimeNode(){
+ return false;
+ }
+
+ public OffsetDateTimeNode asOffsetDateTimeNode(){
+ throw new InvalidPathException("Expected offsetDateTime node");
+ }
+
+
private static boolean isPath(Object o) {
if(o == null || !(o instanceof String)){
return false;
@@ -174,7 +163,8 @@ private static boolean isJson(Object o) {
//
//----------------------------------------------------
public static ValueNode toValueNode(Object o){
- if(o == null) return ValueNode.NULL_NODE;
+
+ if(o == null) return NULL_NODE;
if(o instanceof ValueNode) return (ValueNode)o;
if(o instanceof Class) return createClassNode((Class)o);
else if(isPath(o)) return new PathNode(o.toString(), false, false);
@@ -184,7 +174,9 @@ public static ValueNode toValueNode(Object o){
else if(o instanceof Number) return createNumberNode(o.toString());
else if(o instanceof Boolean) return createBooleanNode(o.toString());
else if(o instanceof Pattern) return createPatternNode((Pattern)o);
+ else if (o instanceof OffsetDateTime) return createOffsetDateTimeNode(o.toString()); //workaround for issue: https://github.com/json-path/JsonPath/issues/613
else throw new JsonPathException("Could not determine value type");
+
}
public static StringNode createStringNode(CharSequence charSequence, boolean escape){
@@ -223,6 +215,12 @@ public static PatternNode createPatternNode(Pattern pattern) {
return new PatternNode(pattern);
}
+ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ public static OffsetDateTimeNode createOffsetDateTimeNode(CharSequence charSequence){
+ return new OffsetDateTimeNode(charSequence);
+ }
+
+
public static UndefinedNode createUndefinedNode() {
return UNDEFINED;
}
@@ -235,624 +233,6 @@ public static ValueNode createPathNode(Path path) {
return new PathNode(path);
}
- //----------------------------------------------------
- //
- // ValueNode Implementations
- //
- //----------------------------------------------------
- public static class PatternNode extends ValueNode {
- private final String pattern;
- private final Pattern compiledPattern;
-
- private PatternNode(CharSequence charSequence) {
- String tmp = charSequence.toString();
- int begin = tmp.indexOf('/');
- int end = tmp.lastIndexOf('/');
- int flags = tmp.endsWith("/i") ? Pattern.CASE_INSENSITIVE : 0;
- this.pattern = tmp.substring(begin + 1, end);
- this.compiledPattern = Pattern.compile(pattern, flags);
- }
-
- public PatternNode(Pattern pattern) {
- this.pattern = pattern.pattern();
- this.compiledPattern = pattern;
- }
-
-
- public Pattern getCompiledPattern() {
- return compiledPattern;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Void.TYPE;
- }
-
- public boolean isPatternNode() {
- return true;
- }
-
- public PatternNode asPatternNode() {
- return this;
- }
-
- @Override
- public String toString() {
-
- String flags = "";
- if((compiledPattern.flags() & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE){
- flags = "i";
- }
- if(!pattern.startsWith("/")){
- return "/" + pattern + "/" + flags;
- } else {
- return pattern;
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof PatternNode)) return false;
-
- PatternNode that = (PatternNode) o;
-
- return !(compiledPattern != null ? !compiledPattern.equals(that.compiledPattern) : that.compiledPattern != null);
-
- }
- }
-
- public static class JsonNode extends ValueNode {
- private final Object json;
- private final boolean parsed;
-
- private JsonNode(CharSequence charSequence) {
- json = charSequence.toString();
- parsed = false;
- }
-
- public JsonNode(Object parsedJson) {
- json = parsedJson;
- parsed = true;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- if(isArray(ctx)) return List.class;
- else if(isMap(ctx)) return Map.class;
- else if(parse(ctx) instanceof Number) return Number.class;
- else if(parse(ctx) instanceof String) return String.class;
- else if(parse(ctx) instanceof Boolean) return Boolean.class;
- else return Void.class;
- }
-
- public boolean isJsonNode() {
- return true;
- }
-
- public JsonNode asJsonNode() {
- return this;
- }
-
- public ValueNode asValueListNode(Predicate.PredicateContext ctx){
- if(!isArray(ctx)){
- return UNDEFINED;
- } else {
- return new ValueListNode(Collections.unmodifiableList((List) parse(ctx)));
- }
- }
-
- public Object parse(Predicate.PredicateContext ctx){
- try {
- return parsed ? json : new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json.toString());
- } catch (ParseException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- public boolean isParsed() {
- return parsed;
- }
-
- public Object getJson() {
- return json;
- }
-
- public boolean isArray(Predicate.PredicateContext ctx) {
- return ctx.configuration().jsonProvider().isArray(parse(ctx));
- }
-
- public boolean isMap(Predicate.PredicateContext ctx) {
- return ctx.configuration().jsonProvider().isMap(parse(ctx));
- }
-
- public int length(Predicate.PredicateContext ctx) {
- return isArray(ctx) ? ctx.configuration().jsonProvider().length(parse(ctx)) : -1;
- }
-
- public boolean isEmpty(Predicate.PredicateContext ctx) {
- if (isArray(ctx) || isMap(ctx)) return ctx.configuration().jsonProvider().length(parse(ctx)) == 0;
- else if((parse(ctx) instanceof String)) return ((String)parse(ctx)).length() == 0;
- return true;
- }
-
- @Override
- public String toString() {
- return json.toString();
- }
-
- public boolean equals(JsonNode jsonNode, Predicate.PredicateContext ctx) {
- if (this == jsonNode) return true;
- return !(json != null ? !json.equals(jsonNode.parse(ctx)) : jsonNode.json != null);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof JsonNode)) return false;
-
- JsonNode jsonNode = (JsonNode) o;
-
- return !(json != null ? !json.equals(jsonNode.json) : jsonNode.json != null);
- }
- }
-
- public static class StringNode extends ValueNode {
- private final String string;
- private boolean useSingleQuote = true;
-
- private StringNode(CharSequence charSequence, boolean escape) {
- if(charSequence.length() > 1){
- char open = charSequence.charAt(0);
- char close = charSequence.charAt(charSequence.length()-1);
-
- if(open == '\'' && close == '\''){
- charSequence = charSequence.subSequence(1, charSequence.length()-1);
- } else if(open == '"' && close == '"'){
- charSequence = charSequence.subSequence(1, charSequence.length()-1);
- useSingleQuote = false;
- }
- }
- string = escape ? Utils.unescape(charSequence.toString()) : charSequence.toString();
- }
-
- @Override
- public NumberNode asNumberNode() {
- BigDecimal number = null;
- try {
- number = new BigDecimal(string);
- } catch (NumberFormatException nfe){
- return NumberNode.NAN;
- }
- return new NumberNode(number);
- }
-
- public String getString() {
- return string;
- }
-
- public int length(){
- return getString().length();
- }
-
- public boolean isEmpty(){
- return getString().isEmpty();
- }
-
- public boolean contains(String str) {
- return getString().contains(str);
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return String.class;
- }
-
- public boolean isStringNode() {
- return true;
- }
-
- public StringNode asStringNode() {
- return this;
- }
-
- @Override
- public String toString() {
- String quote = useSingleQuote ? "'" : "\"";
- return quote + Utils.escape(string, true) + quote;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof StringNode) && !(o instanceof NumberNode)) return false;
-
- StringNode that = ((ValueNode) o).asStringNode();
-
- return !(string != null ? !string.equals(that.getString()) : that.getString() != null);
-
- }
- }
-
- public static class NumberNode extends ValueNode {
-
- public static NumberNode NAN = new NumberNode((BigDecimal)null);
-
- private final BigDecimal number;
-
- private NumberNode(BigDecimal number) {
- this.number = number;
- }
- private NumberNode(CharSequence num) {
- number = new BigDecimal(num.toString());
- }
-
- @Override
- public StringNode asStringNode() {
- return new StringNode(number.toString(), false);
- }
-
- public BigDecimal getNumber() {
- return number;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Number.class;
- }
-
- public boolean isNumberNode() {
- return true;
- }
-
- public NumberNode asNumberNode() {
- return this;
- }
-
- @Override
- public String toString() {
- return number.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof NumberNode) && !(o instanceof StringNode)) return false;
-
- NumberNode that = ((ValueNode)o).asNumberNode();
-
- if(that == NumberNode.NAN){
- return false;
- } else {
- return number.compareTo(that.number) == 0;
- }
- }
- }
- public static class BooleanNode extends ValueNode {
- private final Boolean value;
-
- private BooleanNode(CharSequence boolValue) {
- value = Boolean.parseBoolean(boolValue.toString());
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Boolean.class;
- }
-
- public boolean isBooleanNode() {
- return true;
- }
-
- public BooleanNode asBooleanNode() {
- return this;
- }
-
- public boolean getBoolean() {
- return value;
- }
-
- @Override
- public String toString() {
- return value.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof BooleanNode)) return false;
-
- BooleanNode that = (BooleanNode) o;
-
- return !(value != null ? !value.equals(that.value) : that.value != null);
- }
- }
-
- public static class ClassNode extends ValueNode {
- private final Class clazz;
-
- private ClassNode(Class clazz) {
- this.clazz = clazz;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Class.class;
- }
-
- public boolean isClassNode() {
- return true;
- }
-
- public ClassNode asClassNode() {
- return this;
- }
-
- public Class getClazz() {
- return clazz;
- }
-
- @Override
- public String toString() {
- return clazz.getName();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ClassNode)) return false;
-
- ClassNode that = (ClassNode) o;
-
- return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null);
- }
- }
-
- public static class NullNode extends ValueNode {
-
- private NullNode() {}
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Void.class;
- }
-
- @Override
- public boolean isNullNode() {
- return true;
- }
-
- @Override
- public NullNode asNullNode() {
- return this;
- }
-
- @Override
- public String toString() {
- return "null";
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof NullNode)) return false;
-
- return true;
- }
- }
-
- public static class UndefinedNode extends ValueNode {
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Void.class;
- }
-
- public UndefinedNode asUndefinedNode() {
- return this;
- }
-
- public boolean isUndefinedNode() {
- return true;
- }
-
- @Override
- public boolean equals(Object o) {
- return false;
- }
- }
-
- public static class PredicateNode extends ValueNode {
-
- private final Predicate predicate;
-
- public PredicateNode(Predicate predicate) {
- this.predicate = predicate;
- }
-
- public Predicate getPredicate() {
- return predicate;
- }
-
- public PredicateNode asPredicateNode() {
- return this;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Void.class;
- }
-
- public boolean isPredicateNode() {
- return true;
- }
-
- @Override
- public boolean equals(Object o) {
- return false;
- }
-
- @Override
- public String toString() {
- return predicate.toString();
- }
- }
-
- public static class ValueListNode extends ValueNode implements Iterable {
-
- private List nodes = new ArrayList();
-
- public ValueListNode(Collection> values) {
- for (Object value : values) {
- nodes.add(toValueNode(value));
- }
- }
-
- public boolean contains(ValueNode node){
- return nodes.contains(node);
- }
-
- public boolean subsetof(ValueListNode right) {
- for (ValueNode leftNode : nodes) {
- if (!right.nodes.contains(leftNode)) {
- return false;
- }
- }
- return true;
- }
-
- public List getNodes() {
- return Collections.unmodifiableList(nodes);
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return List.class;
- }
-
- public boolean isValueListNode() {
- return true;
- }
-
- public ValueListNode asValueListNode() {
- return this;
- }
-
- @Override
- public String toString() {
- return "[" + Utils.join(",", nodes) + "]";
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ValueListNode)) return false;
-
- ValueListNode that = (ValueListNode) o;
-
- return !(that != null ? !nodes.equals(that.nodes) : that.nodes != null);
- }
-
- @Override
- public Iterator iterator() {
- return nodes.iterator();
- }
- }
-
- public static class PathNode extends ValueNode {
-
- private static final Logger logger = LoggerFactory.getLogger(PathNode.class);
-
- private final Path path;
- private final boolean existsCheck;
- private final boolean shouldExist;
-
- PathNode(Path path) {
- this(path, false, false);
- }
-
- PathNode(CharSequence charSequence, boolean existsCheck, boolean shouldExist) {
- this(PathCompiler.compile(charSequence.toString()), existsCheck, shouldExist);
- }
-
- PathNode(Path path, boolean existsCheck, boolean shouldExist) {
- this.path = path;
- this.existsCheck = existsCheck;
- this.shouldExist = shouldExist;
- logger.trace("PathNode {} existsCheck: {}", path, existsCheck);
- }
-
- public Path getPath() {
- return path;
- }
-
- public boolean isExistsCheck() {
- return existsCheck;
- }
-
- public boolean shouldExists() {
- return shouldExist;
- }
-
- @Override
- public Class> type(Predicate.PredicateContext ctx) {
- return Void.class;
- }
-
- public boolean isPathNode() {
- return true;
- }
-
- public PathNode asPathNode() {
- return this;
- }
-
- public PathNode asExistsCheck(boolean shouldExist) {
- return new PathNode(path, true, shouldExist);
- }
-
- @Override
- public String toString() {
- return existsCheck && ! shouldExist ? Utils.concat("!" , path.toString()) : path.toString();
- }
-
- public ValueNode evaluate(Predicate.PredicateContext ctx) {
- if (isExistsCheck()) {
- try {
- Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build();
- Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false);
- return result == JsonProvider.UNDEFINED ? ValueNode.FALSE : ValueNode.TRUE;
- } catch (PathNotFoundException e) {
- return ValueNode.FALSE;
- }
- } else {
- try {
- Object res;
- if (ctx instanceof PredicateContextImpl) {
- //This will use cache for document ($) queries
- PredicateContextImpl ctxi = (PredicateContextImpl) ctx;
- res = ctxi.evaluate(path);
- } else {
- Object doc = path.isRootPath() ? ctx.root() : ctx.item();
- res = path.evaluate(doc, ctx.root(), ctx.configuration()).getValue();
- }
- res = ctx.configuration().jsonProvider().unwrap(res);
-
- if (res instanceof Number) return ValueNode.createNumberNode(res.toString());
- else if (res instanceof BigDecimal) return ValueNode.createNumberNode(res.toString());
- else if (res instanceof String) return ValueNode.createStringNode(res.toString(), false);
- else if (res instanceof Boolean) return ValueNode.createBooleanNode(res.toString());
- else if (res == null) return ValueNode.NULL_NODE;
- else if (ctx.configuration().jsonProvider().isArray(res)) return ValueNode.createJsonNode(res);
- else if (ctx.configuration().jsonProvider().isMap(res)) return ValueNode.createJsonNode(res);
- else throw new JsonPathException("Could not convert " + res.toString() + " to a ValueNode");
- } catch (PathNotFoundException e) {
- return ValueNode.UNDEFINED;
- }
- }
- }
-
-
- }
}
+
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java
new file mode 100644
index 000000000..3f459a8c1
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java
@@ -0,0 +1,712 @@
+package com.jayway.jsonpath.internal.filter;
+
+import java.math.BigDecimal;
+import java.time.OffsetDateTime;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.Option;
+import com.jayway.jsonpath.PathNotFoundException;
+import com.jayway.jsonpath.Predicate;
+import com.jayway.jsonpath.internal.Path;
+import com.jayway.jsonpath.internal.Utils;
+import com.jayway.jsonpath.internal.path.PathCompiler;
+import com.jayway.jsonpath.internal.path.PredicateContextImpl;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+import net.minidev.json.parser.JSONParser;
+import net.minidev.json.parser.ParseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Moved these nodes out of the ValueNode abstract class.
+ * This is to avoid this possible issue:
+ *
+ * Classes that refer to their own subclasses in their static initializers or in static fields.
+ * Such references can cause JVM-level deadlocks in multithreaded environment, when
+ * one thread tries to load superclass and another thread tries to load subclass at the same time.
+ */
+public interface ValueNodes {
+
+ NullNode NULL_NODE = new NullNode();
+ BooleanNode TRUE = new BooleanNode("true");
+ BooleanNode FALSE = new BooleanNode("false");
+ UndefinedNode UNDEFINED = new UndefinedNode();
+
+ //----------------------------------------------------
+ //
+ // ValueNode Implementations
+ //
+ //----------------------------------------------------
+ class PatternNode extends ValueNode {
+ private final String pattern;
+ private final Pattern compiledPattern;
+ private final String flags;
+
+ PatternNode(CharSequence charSequence) {
+ String tmp = charSequence.toString();
+ int begin = tmp.indexOf('/');
+ int end = tmp.lastIndexOf('/');
+ this.pattern = tmp.substring(begin + 1, end);
+ int flagsIndex = end + 1;
+ this.flags = tmp.length() > flagsIndex ? tmp.substring(flagsIndex) : "";
+ this.compiledPattern = Pattern.compile(pattern, PatternFlag.parseFlags(flags.toCharArray()));
+ }
+
+ PatternNode(Pattern pattern) {
+ this.pattern = pattern.pattern();
+ this.compiledPattern = pattern;
+ this.flags = PatternFlag.parseFlags(pattern.flags());
+ }
+
+
+ Pattern getCompiledPattern() {
+ return compiledPattern;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Void.TYPE;
+ }
+
+ public boolean isPatternNode() {
+ return true;
+ }
+
+ public PatternNode asPatternNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+
+ if(!pattern.startsWith("/")){
+ return "/" + pattern + "/" + flags;
+ } else {
+ return pattern;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PatternNode)) return false;
+
+ PatternNode that = (PatternNode) o;
+
+ return !(compiledPattern != null ? !compiledPattern.equals(that.compiledPattern) : that.compiledPattern != null);
+
+ }
+ }
+
+ class JsonNode extends ValueNode {
+ private final Object json;
+ private final boolean parsed;
+
+ JsonNode(CharSequence charSequence) {
+ json = charSequence.toString();
+ parsed = false;
+ }
+
+ JsonNode(Object parsedJson) {
+ json = parsedJson;
+ parsed = true;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ if(isArray(ctx)) return List.class;
+ else if(isMap(ctx)) return Map.class;
+ else if(parse(ctx) instanceof Number) return Number.class;
+ else if(parse(ctx) instanceof String) return String.class;
+ else if(parse(ctx) instanceof Boolean) return Boolean.class;
+ else return Void.class;
+ }
+
+ public boolean isJsonNode() {
+ return true;
+ }
+
+ public JsonNode asJsonNode() {
+ return this;
+ }
+
+ public ValueNode asValueListNode(Predicate.PredicateContext ctx){
+ if(!isArray(ctx)){
+ return UNDEFINED;
+ } else {
+ return new ValueListNode(Collections.unmodifiableList((List) parse(ctx)));
+ }
+ }
+
+ public Object parse(Predicate.PredicateContext ctx){
+ try {
+ return parsed ? json : new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json.toString());
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public boolean isParsed() {
+ return parsed;
+ }
+
+ public Object getJson() {
+ return json;
+ }
+
+ public boolean isArray(Predicate.PredicateContext ctx) {
+ return parse(ctx) instanceof List;
+ }
+
+ public boolean isMap(Predicate.PredicateContext ctx) {
+ return parse(ctx) instanceof Map;
+ }
+
+ public int length(Predicate.PredicateContext ctx) {
+ return isArray(ctx) ? ((List>) parse(ctx)).size() : -1;
+ }
+
+ public boolean isEmpty(Predicate.PredicateContext ctx) {
+ if (isArray(ctx) || isMap(ctx)) return ((Collection>) parse(ctx)).size() == 0;
+ else if((parse(ctx) instanceof String)) return ((String)parse(ctx)).length() == 0;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return json.toString();
+ }
+
+ public boolean equals(JsonNode jsonNode, Predicate.PredicateContext ctx) {
+ if (this == jsonNode) return true;
+ return !(json != null ? !json.equals(jsonNode.parse(ctx)) : jsonNode.json != null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof JsonNode)) return false;
+
+ JsonNode jsonNode = (JsonNode) o;
+
+ return !(json != null ? !json.equals(jsonNode.json) : jsonNode.json != null);
+ }
+ }
+
+ class StringNode extends ValueNode {
+ private final String string;
+ private boolean useSingleQuote = true;
+
+ StringNode(CharSequence charSequence, boolean escape) {
+ if (escape && charSequence.length() > 1) {
+ char open = charSequence.charAt(0);
+ char close = charSequence.charAt(charSequence.length()-1);
+ if (open == '\'' && close == '\'') {
+ charSequence = charSequence.subSequence(1, charSequence.length()-1);
+ } else if (open == '"' && close == '"') {
+ charSequence = charSequence.subSequence(1, charSequence.length()-1);
+ useSingleQuote = false;
+ }
+ string = Utils.unescape(charSequence.toString());
+ } else {
+ string = charSequence.toString();
+ }
+ }
+
+ @Override
+ public NumberNode asNumberNode() {
+ BigDecimal number = null;
+ try {
+ number = new BigDecimal(string);
+ } catch (NumberFormatException nfe){
+ return NumberNode.NAN;
+ }
+ return new NumberNode(number);
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public int length(){
+ return getString().length();
+ }
+
+ public boolean isEmpty(){
+ return getString().isEmpty();
+ }
+
+ public boolean contains(String str) {
+ return getString().contains(str);
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return String.class;
+ }
+
+ public boolean isStringNode() {
+ return true;
+ }
+
+ public StringNode asStringNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ String quote = useSingleQuote ? "'" : "\"";
+ return quote + Utils.escape(string, true) + quote;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof StringNode) && !(o instanceof NumberNode)) return false;
+
+ StringNode that = ((ValueNode) o).asStringNode();
+
+ return !(string != null ? !string.equals(that.getString()) : that.getString() != null);
+
+ }
+ }
+
+ class NumberNode extends ValueNode {
+
+ public static NumberNode NAN = new NumberNode((BigDecimal)null);
+
+ private final BigDecimal number;
+
+ NumberNode(BigDecimal number) {
+ this.number = number;
+ }
+ NumberNode(CharSequence num) {
+ number = new BigDecimal(num.toString());
+ }
+
+ @Override
+ public StringNode asStringNode() {
+ return new StringNode(number.toString(), false);
+ }
+
+ public BigDecimal getNumber() {
+ return number;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Number.class;
+ }
+
+ public boolean isNumberNode() {
+ return true;
+ }
+
+ public NumberNode asNumberNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return number.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NumberNode) && !(o instanceof StringNode)) return false;
+
+ NumberNode that = ((ValueNode)o).asNumberNode();
+
+ if(that == NumberNode.NAN){
+ return false;
+ } else {
+ return number.compareTo(that.number) == 0;
+ }
+ }
+ }
+
+ //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ class OffsetDateTimeNode extends ValueNode {
+
+ private final OffsetDateTime dateTime;
+
+
+ OffsetDateTimeNode(OffsetDateTime dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ OffsetDateTimeNode(CharSequence date) {
+ dateTime = OffsetDateTime.parse(date);
+ }
+
+ @Override
+ public StringNode asStringNode() {
+ return new StringNode(dateTime.toString(), false);
+ }
+
+ public OffsetDateTime getDate() {
+ return dateTime;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return OffsetDateTimeNode.class;
+ }
+
+ public boolean isOffsetDateTimeNode() {
+ return true;
+ }
+
+ public OffsetDateTimeNode asOffsetDateTimeNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return dateTime.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof OffsetDateTimeNode) && !(o instanceof StringNode)) return false;
+ OffsetDateTimeNode that = ((ValueNode)o).asOffsetDateTimeNode();
+ return dateTime.compareTo(that.dateTime) == 0;
+ }
+ }
+
+
+
+ class BooleanNode extends ValueNode {
+ private final Boolean value;
+
+ private BooleanNode(CharSequence boolValue) {
+ value = Boolean.parseBoolean(boolValue.toString());
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Boolean.class;
+ }
+
+ public boolean isBooleanNode() {
+ return true;
+ }
+
+ public BooleanNode asBooleanNode() {
+ return this;
+ }
+
+ public boolean getBoolean() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BooleanNode)) return false;
+
+ BooleanNode that = (BooleanNode) o;
+
+ return !(value != null ? !value.equals(that.value) : that.value != null);
+ }
+ }
+
+
+
+
+
+ class ClassNode extends ValueNode {
+ private final Class clazz;
+
+ ClassNode(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Class.class;
+ }
+
+ public boolean isClassNode() {
+ return true;
+ }
+
+ public ClassNode asClassNode() {
+ return this;
+ }
+
+ public Class getClazz() {
+ return clazz;
+ }
+
+ @Override
+ public String toString() {
+ return clazz.getName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClassNode)) return false;
+
+ ClassNode that = (ClassNode) o;
+
+ return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null);
+ }
+ }
+
+ class NullNode extends ValueNode {
+
+ private NullNode() {}
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Void.class;
+ }
+
+ @Override
+ public boolean isNullNode() {
+ return true;
+ }
+
+ @Override
+ public NullNode asNullNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "null";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NullNode)) return false;
+
+ return true;
+ }
+ }
+
+ class UndefinedNode extends ValueNode {
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Void.class;
+ }
+
+ public UndefinedNode asUndefinedNode() {
+ return this;
+ }
+
+ public boolean isUndefinedNode() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return false;
+ }
+ }
+
+ class PredicateNode extends ValueNode {
+
+ private final Predicate predicate;
+
+ public PredicateNode(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ public Predicate getPredicate() {
+ return predicate;
+ }
+
+ public PredicateNode asPredicateNode() {
+ return this;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Void.class;
+ }
+
+ public boolean isPredicateNode() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return predicate.toString();
+ }
+ }
+
+ class ValueListNode extends ValueNode implements Iterable {
+
+ private List nodes = new ArrayList();
+
+ public ValueListNode(Collection> values) {
+ for (Object value : values) {
+ nodes.add(toValueNode(value));
+ }
+ }
+
+ public boolean contains(ValueNode node){
+ return nodes.contains(node);
+ }
+
+ public boolean subsetof(ValueListNode right) {
+ for (ValueNode leftNode : nodes) {
+ if (!right.nodes.contains(leftNode)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public List getNodes() {
+ return Collections.unmodifiableList(nodes);
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return List.class;
+ }
+
+ public boolean isValueListNode() {
+ return true;
+ }
+
+ public ValueListNode asValueListNode() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + Utils.join(",", nodes) + "]";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ValueListNode)) return false;
+
+ ValueListNode that = (ValueListNode) o;
+
+ return nodes.equals(that.nodes);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return nodes.iterator();
+ }
+ }
+
+ class PathNode extends ValueNode {
+
+ private static final Logger logger = LoggerFactory.getLogger(PathNode.class);
+
+ private final Path path;
+ private final boolean existsCheck;
+ private final boolean shouldExist;
+
+ PathNode(Path path) {
+ this(path, false, false);
+ }
+
+ PathNode(CharSequence charSequence, boolean existsCheck, boolean shouldExist) {
+ this(PathCompiler.compile(charSequence.toString()), existsCheck, shouldExist);
+ }
+
+ PathNode(Path path, boolean existsCheck, boolean shouldExist) {
+ this.path = path;
+ this.existsCheck = existsCheck;
+ this.shouldExist = shouldExist;
+ logger.trace("PathNode {} existsCheck: {}", path, existsCheck);
+ }
+
+ public Path getPath() {
+ return path;
+ }
+
+ public boolean isExistsCheck() {
+ return existsCheck;
+ }
+
+ public boolean shouldExists() {
+ return shouldExist;
+ }
+
+ @Override
+ public Class> type(Predicate.PredicateContext ctx) {
+ return Void.class;
+ }
+
+ public boolean isPathNode() {
+ return true;
+ }
+
+ public PathNode asPathNode() {
+ return this;
+ }
+
+ public PathNode asExistsCheck(boolean shouldExist) {
+ return new PathNode(path, true, shouldExist);
+ }
+
+ @Override
+ public String toString() {
+ return existsCheck && ! shouldExist ? Utils.concat("!" , path.toString()) : path.toString();
+ }
+
+ public ValueNode evaluate(Predicate.PredicateContext ctx) {
+ if (isExistsCheck()) {
+ try {
+ Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build();
+ Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false);
+ return result == JsonProvider.UNDEFINED ? FALSE : TRUE;
+ } catch (PathNotFoundException e) {
+ return FALSE;
+ }
+ } else {
+ try {
+ Object res;
+ if (ctx instanceof PredicateContextImpl) {
+ //This will use cache for document ($) queries
+ PredicateContextImpl ctxi = (PredicateContextImpl) ctx;
+ res = ctxi.evaluate(path);
+ } else {
+ Object doc = path.isRootPath() ? ctx.root() : ctx.item();
+ res = path.evaluate(doc, ctx.root(), ctx.configuration()).getValue();
+ }
+ res = ctx.configuration().jsonProvider().unwrap(res);
+
+ if (res instanceof Number) return ValueNode.createNumberNode(res.toString());
+ else if (res instanceof String) return ValueNode.createStringNode(res.toString(), false);
+ else if (res instanceof Boolean) return ValueNode.createBooleanNode(res.toString());
+ else if (res instanceof OffsetDateTime) return ValueNode.createOffsetDateTimeNode(res.toString()); //workaround for issue: https://github.com/json-path/JsonPath/issues/613
+ else if (res == null) return NULL_NODE;
+ else if (ctx.configuration().jsonProvider().isArray(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, List.class, ctx.configuration()));
+ else if (ctx.configuration().jsonProvider().isMap(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, Map.class, ctx.configuration()));
+ else throw new JsonPathException("Could not convert " + res.getClass().toString()+":"+ res.toString() + " to a ValueNode");
+ } catch (PathNotFoundException e) {
+ return UNDEFINED;
+ }
+ }
+ }
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
index 75824ff87..94a3e3572 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
@@ -1,11 +1,15 @@
package com.jayway.jsonpath.internal.function;
+import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.function.latebinding.ILateBindingValue;
-import com.jayway.jsonpath.internal.function.latebinding.PathLateBindingValue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
- * Created by matt@mjgreenwood.net on 12/10/15.
+ * Defines a parameter as passed to a function with late binding support for lazy evaluation.
*/
public class Parameter {
private ParamType type;
@@ -65,4 +69,69 @@ public String getJson() {
public void setJson(String json) {
this.json = json;
}
+
+ public ILateBindingValue getILateBingValue(){
+ return lateBinding;
+ }
+
+ /**
+ * Translate the collection of parameters into a collection of values of type T.
+ *
+ * @param type
+ * The type to translate the collection into.
+ *
+ * @param ctx
+ * Context.
+ *
+ * @param parameters
+ * Collection of parameters.
+ *
+ * @param
+ * Type T returned as a List of T.
+ *
+ * @return
+ * List of T either empty or containing contents.
+ */
+ public static List toList(final Class type, final EvaluationContext ctx, final List parameters) {
+ List values = new ArrayList();
+ if (null != parameters) {
+ for (Parameter param : parameters) {
+ consume(type, ctx, values, param.getValue());
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Either consume the object as an array and add each element to the collection, or alternatively add each element
+ *
+ * @param expectedType
+ * the expected class type to consume, if null or not of this type the element is not added to the array.
+ *
+ * @param ctx
+ * the JSON context to determine if this is an array or value.
+ *
+ * @param collection
+ * The collection to append into.
+ *
+ * @param value
+ * The value to evaluate.
+ */
+ public static void consume(Class expectedType, EvaluationContext ctx, Collection collection, Object value) {
+ if (ctx.configuration().jsonProvider().isArray(value)) {
+ for (Object o : ctx.configuration().jsonProvider().toIterable(value)) {
+ if (o != null && expectedType.isAssignableFrom(o.getClass())) {
+ collection.add(o);
+ } else if (o != null && expectedType == String.class) {
+ collection.add(o.toString());
+ }
+ }
+ } else {
+ if (value != null && expectedType.isAssignableFrom(value.getClass())) {
+ collection.add(value);
+ } else if (value != null && expectedType == String.class) {
+ collection.add(value.toString());
+ }
+ }
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
index 7f0a38023..ac3a353ba 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
@@ -30,7 +30,7 @@ public interface PathFunction {
* Eval context, state bag used as the path is traversed, maintains the result of executing
*
* @param parameters
- * @return
+ * @return result
*/
Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters);
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
index a35f61bf3..3a31151f2 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
@@ -2,11 +2,15 @@
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.internal.function.json.Append;
+import com.jayway.jsonpath.internal.function.json.KeySetFunction;
import com.jayway.jsonpath.internal.function.numeric.Average;
import com.jayway.jsonpath.internal.function.numeric.Max;
import com.jayway.jsonpath.internal.function.numeric.Min;
import com.jayway.jsonpath.internal.function.numeric.StandardDeviation;
import com.jayway.jsonpath.internal.function.numeric.Sum;
+import com.jayway.jsonpath.internal.function.sequence.First;
+import com.jayway.jsonpath.internal.function.sequence.Index;
+import com.jayway.jsonpath.internal.function.sequence.Last;
import com.jayway.jsonpath.internal.function.text.Concatenate;
import com.jayway.jsonpath.internal.function.text.Length;
@@ -21,7 +25,6 @@
* Leverages the function's name in order to determine which function to execute which is maintained internally
* here via a static map
*
- * Created by mattg on 6/27/15.
*/
public class PathFunctionFactory {
@@ -42,9 +45,15 @@ public class PathFunctionFactory {
map.put("concat", Concatenate.class);
// JSON Entity Functions
- map.put("length", Length.class);
+ map.put(Length.TOKEN_NAME, Length.class);
map.put("size", Length.class);
map.put("append", Append.class);
+ map.put("keys", KeySetFunction.class);
+
+ // Sequential Functions
+ map.put("first", First.class);
+ map.put("last", Last.class);
+ map.put("index", Index.class);
FUNCTIONS = Collections.unmodifiableMap(map);
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/json/KeySetFunction.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/json/KeySetFunction.java
new file mode 100644
index 000000000..049d6de0e
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/json/KeySetFunction.java
@@ -0,0 +1,23 @@
+package com.jayway.jsonpath.internal.function.json;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.List;
+
+/**
+ * Author: Sergey Saiyan sergey.sova42@gmail.com
+ * Created at 21/02/2018.
+ */
+public class KeySetFunction implements PathFunction {
+
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ if (ctx.configuration().jsonProvider().isMap(model)) {
+ return ctx.configuration().jsonProvider().getPropertyKeys(model);
+ }
+ return null;
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
index 0f653ad16..a72442931 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/JsonLateBindingValue.java
@@ -34,7 +34,7 @@ public JsonLateBindingValue(JsonProvider jsonProvider, Parameter jsonParameter)
* Evaluate the JSON document at the point of need using the JSON parameter and associated document model which may
* itself originate from yet another function thus recursively invoking late binding methods.
*
- * @return
+ * @return the late value
*/
@Override
public Object get() {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
index 04181cb77..38139f3a8 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/latebinding/PathLateBindingValue.java
@@ -16,7 +16,8 @@
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.internal.Path;
-import com.jayway.jsonpath.internal.function.ParamType;
+
+import java.util.Objects;
/**
* Defines the contract for late bindings, provides document state and enough context to perform the evaluation at a later
@@ -27,21 +28,31 @@
*/
public class PathLateBindingValue implements ILateBindingValue {
private final Path path;
- private final Object rootDocument;
+ private final String rootDocument;
private final Configuration configuration;
-
+ private final Object result;
public PathLateBindingValue(final Path path, final Object rootDocument, final Configuration configuration) {
this.path = path;
- this.rootDocument = rootDocument;
+ this.rootDocument = rootDocument.toString();
this.configuration = configuration;
+ this.result = path.evaluate(rootDocument, rootDocument, configuration).getValue();
}
/**
- * Evaluate the expression at the point of need for Path type expressions
*
- * @return
+ * @return the late value
*/
public Object get() {
- return path.evaluate(rootDocument, rootDocument, configuration).getValue();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PathLateBindingValue that = (PathLateBindingValue) o;
+ return Objects.equals(path, that.path) &&
+ rootDocument.equals(that.rootDocument) &&
+ Objects.equals(configuration, that.configuration);
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
index fe97628ca..cbc9f7bc9 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
@@ -48,12 +48,9 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
}
}
if (parameters != null) {
- for (Parameter param : parameters) {
- Object value = param.getValue();
- if (null != value && value instanceof Number) {
- count++;
- next((Number)value);
- }
+ for (Number value : Parameter.toList(Number.class, ctx, parameters)) {
+ count++;
+ next(value);
}
}
if (count != 0) {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java
new file mode 100644
index 000000000..24c87e136
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/AbstractSequenceAggregation.java
@@ -0,0 +1,47 @@
+package com.jayway.jsonpath.internal.function.sequence;
+
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines the pattern for taking item from collection of JSONArray by index
+ *
+ * Created by git9527 on 6/11/22.
+ */
+public abstract class AbstractSequenceAggregation implements PathFunction {
+
+ protected abstract int targetIndex(EvaluationContext ctx, List parameters);
+
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ if(ctx.configuration().jsonProvider().isArray(model)){
+
+ Iterable> objects = ctx.configuration().jsonProvider().toIterable(model);
+ List objectList = new ArrayList<>();
+ objects.forEach(objectList::add);
+ int targetIndex = this.targetIndex(ctx, parameters);
+ if (targetIndex >= 0) {
+ return objectList.get(targetIndex);
+ } else {
+ int realIndex = objectList.size() + targetIndex;
+ if (realIndex > 0) {
+ return objectList.get(realIndex);
+ } else {
+ throw new JsonPathException("Target index:" + targetIndex + " larger than object count:" + objectList.size());
+ }
+ }
+ }
+ throw new JsonPathException("Aggregation function attempted to calculate value using empty array");
+ }
+
+ protected int getIndexFromParameters(EvaluationContext ctx, List parameters) {
+ List numbers = Parameter.toList(Number.class, ctx, parameters);
+ return numbers.get(0).intValue();
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java
new file mode 100644
index 000000000..4f92ef254
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/First.java
@@ -0,0 +1,18 @@
+package com.jayway.jsonpath.internal.function.sequence;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.function.Parameter;
+
+import java.util.List;
+
+/**
+ * Take the first item from collection of JSONArray
+ *
+ * Created by git9527 on 6/11/22.
+ */
+public class First extends AbstractSequenceAggregation {
+ @Override
+ protected int targetIndex(EvaluationContext ctx, List parameters) {
+ return 0;
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java
new file mode 100644
index 000000000..b0ebd8a6a
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Index.java
@@ -0,0 +1,18 @@
+package com.jayway.jsonpath.internal.function.sequence;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.function.Parameter;
+
+import java.util.List;
+
+/**
+ * Take the index from first Parameter, then the item from collection of JSONArray by index
+ *
+ * Created by git9527 on 6/11/22.
+ */
+public class Index extends AbstractSequenceAggregation {
+ @Override
+ protected int targetIndex(EvaluationContext ctx, List parameters) {
+ return getIndexFromParameters(ctx, parameters);
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java
new file mode 100644
index 000000000..39b6345eb
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/sequence/Last.java
@@ -0,0 +1,18 @@
+package com.jayway.jsonpath.internal.function.sequence;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.function.Parameter;
+
+import java.util.List;
+
+/**
+ * Take the first item from collection of JSONArray
+ *
+ * Created by git9527 on 6/11/22.
+ */
+public class Last extends AbstractSequenceAggregation {
+ @Override
+ protected int targetIndex(EvaluationContext ctx, List parameters) {
+ return -1;
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
index 4cf05524d..d499afef3 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Concatenate.java
@@ -11,12 +11,11 @@
* String function concat - simple takes a list of arguments and/or an array and concatenates them together to form a
* single string
*
- * Created by mgreenwood on 12/11/15.
*/
public class Concatenate implements PathFunction {
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
- StringBuffer result = new StringBuffer();
+ StringBuilder result = new StringBuilder();
if(ctx.configuration().jsonProvider().isArray(model)){
Iterable> objects = ctx.configuration().jsonProvider().toIterable(model);
for (Object obj : objects) {
@@ -26,11 +25,8 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
}
}
if (parameters != null) {
- for (Parameter param : parameters) {
- Object value = param.getValue();
- if (value != null) {
- result.append(value.toString());
- }
+ for (String value : Parameter.toList(String.class, ctx, parameters)) {
+ result.append(value);
}
}
return result.toString();
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
index e70f4e3f1..ea8ab1ffe 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
@@ -1,9 +1,14 @@
package com.jayway.jsonpath.internal.function.text;
import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.function.Parameter;
import com.jayway.jsonpath.internal.function.PathFunction;
+import com.jayway.jsonpath.internal.path.CompiledPath;
+import com.jayway.jsonpath.internal.path.PathToken;
+import com.jayway.jsonpath.internal.path.RootPathToken;
+import com.jayway.jsonpath.internal.path.WildcardPathToken;
import java.util.List;
@@ -14,9 +19,53 @@
*/
public class Length implements PathFunction {
+ public static final String TOKEN_NAME = "length";
+
+ /**
+ * When we calculate the length of a path, what we're asking is given the node we land on how many children does it
+ * have. Thus when we wrote the original query what we really wanted was $..book.length() or $.length($..book.*)
+ *
+ * @param currentPath
+ * The current path location inclusive of the function name
+ * @param parent
+ * The path location above the current function
+ *
+ * @param model
+ * The JSON model as input to this particular function
+ *
+ * @param ctx
+ * Eval context, state bag used as the path is traversed, maintains the result of executing
+ *
+ * @param parameters
+ * @return
+ */
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
- if(ctx.configuration().jsonProvider().isArray(model)){
+ if (null != parameters && parameters.size() > 0) {
+
+ // Set the tail of the first parameter, when its not a function path parameter (which wouldn't make sense
+ // for length - to the wildcard such that we request all of its children so we can get back an array and
+ // take its length.
+ Parameter lengthOfParameter = parameters.get(0);
+ if (!lengthOfParameter.getPath().isFunctionPath()) {
+ Path path = lengthOfParameter.getPath();
+ if (path instanceof CompiledPath) {
+ RootPathToken root = ((CompiledPath) path).getRoot();
+ PathToken tail = root.getNext();
+ while (null != tail && null != tail.getNext()) {
+ tail = tail.getNext();
+ }
+ if (null != tail) {
+ tail.setNext(new WildcardPathToken());
+ }
+ }
+ }
+ Object innerModel = parameters.get(0).getPath().evaluate(model, model, ctx.configuration()).getValue();
+ if (ctx.configuration().jsonProvider().isArray(innerModel)) {
+ return ctx.configuration().jsonProvider().length(innerModel);
+ }
+ }
+ if (ctx.configuration().jsonProvider().isArray(model)) {
return ctx.configuration().jsonProvider().length(model);
} else if(ctx.configuration().jsonProvider().isMap(model)){
return ctx.configuration().jsonProvider().length(model);
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java
new file mode 100644
index 000000000..3e4b36e2e
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexToken.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 the original author or authors.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.internal.PathRef;
+
+import static java.lang.String.format;
+
+public class ArrayIndexToken extends ArrayPathToken {
+
+ private final ArrayIndexOperation arrayIndexOperation;
+
+ ArrayIndexToken(final ArrayIndexOperation arrayIndexOperation) {
+ this.arrayIndexOperation = arrayIndexOperation;
+ }
+
+ @Override
+ public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ if (!checkArrayModel(currentPath, model, ctx))
+ return;
+ if (arrayIndexOperation.isSingleIndexOperation()) {
+ handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx);
+ } else {
+ for (Integer index : arrayIndexOperation.indexes()) {
+ handleArrayIndex(index, currentPath, model, ctx);
+ }
+ }
+ }
+
+ @Override
+ public String getPathFragment() {
+ return arrayIndexOperation.toString();
+ }
+
+ @Override
+ public boolean isTokenDefinite() {
+ return arrayIndexOperation.isSingleIndexOperation();
+ }
+
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
index 05d29d765..e3a913aee 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayPathToken.java
@@ -15,150 +15,12 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException;
-import com.jayway.jsonpath.internal.PathRef;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import static java.lang.String.format;
-/**
- *
- */
-public class ArrayPathToken extends PathToken {
-
- private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class);
-
- private final ArraySliceOperation arraySliceOperation;
- private final ArrayIndexOperation arrayIndexOperation;
-
- ArrayPathToken(final ArraySliceOperation arraySliceOperation) {
- this.arraySliceOperation = arraySliceOperation;
- this.arrayIndexOperation = null;
- }
-
- ArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
- this.arrayIndexOperation = arrayIndexOperation;
- this.arraySliceOperation = null;
- }
-
- @Override
- public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
- if (! checkArrayModel(currentPath, model, ctx))
- return;
- if(arraySliceOperation != null){
- evaluateSliceOperation(currentPath, parent, model, ctx);
- } else {
- evaluateIndexOperation(currentPath, parent, model, ctx);
- }
-
- }
-
- public void evaluateIndexOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
-
- if (! checkArrayModel(currentPath, model, ctx))
- return;
-
- if(arrayIndexOperation.isSingleIndexOperation()){
- handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx);
- } else {
- for (Integer index : arrayIndexOperation.indexes()) {
- handleArrayIndex(index, currentPath, model, ctx);
- }
- }
- }
-
- public void evaluateSliceOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
-
- if (! checkArrayModel(currentPath, model, ctx))
- return;
-
- switch (arraySliceOperation.operation()) {
- case SLICE_FROM:
- sliceFrom(arraySliceOperation, currentPath, parent, model, ctx);
- break;
- case SLICE_BETWEEN:
- sliceBetween(arraySliceOperation, currentPath, parent, model, ctx);
- break;
- case SLICE_TO:
- sliceTo(arraySliceOperation, currentPath, parent, model, ctx);
- break;
- }
- }
-
- public void sliceFrom(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
- int length = ctx.jsonProvider().length(model);
- int from = operation.from();
- if (from < 0) {
- //calculate slice start from array length
- from = length + from;
- }
- from = Math.max(0, from);
-
- logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString());
-
- if (length == 0 || from >= length) {
- return;
- }
- for (int i = from; i < length; i++) {
- handleArrayIndex(i, currentPath, model, ctx);
- }
- }
-
- public void sliceBetween(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
- int length = ctx.jsonProvider().length(model);
- int from = operation.from();
- int to = operation.to();
-
- to = Math.min(length, to);
-
- if (from >= to || length == 0) {
- return;
- }
-
- logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString());
-
- for (int i = from; i < to; i++) {
- handleArrayIndex(i, currentPath, model, ctx);
- }
- }
-
- public void sliceTo(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
- int length = ctx.jsonProvider().length(model);
- if (length == 0) {
- return;
- }
- int to = operation.to();
- if (to < 0) {
- //calculate slice end from array length
- to = length + to;
- }
- to = Math.min(length, to);
-
- logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString());
-
- for (int i = 0; i < to; i++) {
- handleArrayIndex(i, currentPath, model, ctx);
- }
- }
-
- @Override
- public String getPathFragment() {
- if(arrayIndexOperation != null){
- return arrayIndexOperation.toString();
- } else {
- return arraySliceOperation.toString();
- }
- }
-
- @Override
- public boolean isTokenDefinite() {
- if(arrayIndexOperation != null){
- return arrayIndexOperation.isSingleIndexOperation();
- } else {
- return false;
- }
- }
+public abstract class ArrayPathToken extends PathToken {
/**
* Check if model is non-null and array.
@@ -171,14 +33,16 @@ public boolean isTokenDefinite() {
*/
protected boolean checkArrayModel(String currentPath, Object model, EvaluationContextImpl ctx) {
if (model == null){
- if (! isUpstreamDefinite()) {
+ if (!isUpstreamDefinite()
+ || ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)) {
return false;
} else {
throw new PathNotFoundException("The path " + currentPath + " is null");
}
}
if (!ctx.jsonProvider().isArray(model)) {
- if (! isUpstreamDefinite()) {
+ if (!isUpstreamDefinite()
+ || ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)) {
return false;
} else {
throw new PathNotFoundException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model));
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
index e6dcb8c1a..185af5121 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceOperation.java
@@ -58,19 +58,19 @@ public static ArraySliceOperation parse(String operation){
Integer tempFrom = tryRead(tokens, 0);
Integer tempTo = tryRead(tokens, 1);
- Operation tempOperpation;
-
- if(tempFrom != null && tempTo == null){
- tempOperpation = Operation.SLICE_FROM;
- } else if(tempFrom != null && tempTo != null){
- tempOperpation = Operation.SLICE_BETWEEN;
- } else if(tempFrom == null && tempTo != null){
- tempOperpation = Operation.SLICE_TO;
+ Operation tempOperation;
+
+ if (tempFrom != null && tempTo == null) {
+ tempOperation = Operation.SLICE_FROM;
+ } else if (tempFrom != null) {
+ tempOperation = Operation.SLICE_BETWEEN;
+ } else if (tempTo != null) {
+ tempOperation = Operation.SLICE_TO;
} else {
throw new InvalidPathException("Failed to parse SliceOperation: " + operation);
}
- return new ArraySliceOperation(tempFrom, tempTo, tempOperpation);
+ return new ArraySliceOperation(tempFrom, tempTo, tempOperation);
}
private static Integer tryRead(String[] tokens, int idx){
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java
new file mode 100644
index 000000000..af43e7ebf
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 the original author or authors.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jayway.jsonpath.internal.path;
+
+import com.jayway.jsonpath.internal.PathRef;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ArraySliceToken extends ArrayPathToken {
+
+ private static final Logger logger = LoggerFactory.getLogger(ArraySliceToken.class);
+
+ private final ArraySliceOperation operation;
+
+ ArraySliceToken(final ArraySliceOperation operation) {
+ this.operation = operation;
+ }
+
+ @Override
+ public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ if (!checkArrayModel(currentPath, model, ctx))
+ return;
+ switch (operation.operation()) {
+ case SLICE_FROM:
+ sliceFrom(currentPath, parent, model, ctx);
+ break;
+ case SLICE_BETWEEN:
+ sliceBetween(currentPath, parent, model, ctx);
+ break;
+ case SLICE_TO:
+ sliceTo(currentPath, parent, model, ctx);
+ break;
+ }
+ }
+
+ private void sliceFrom(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ int length = ctx.jsonProvider().length(model);
+ int from = operation.from();
+ if (from < 0) {
+ //calculate slice start from array length
+ from = length + from;
+ }
+ from = Math.max(0, from);
+
+ logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString());
+
+ if (length == 0 || from >= length) {
+ return;
+ }
+ for (int i = from; i < length; i++) {
+ handleArrayIndex(i, currentPath, model, ctx);
+ }
+ }
+
+ private void sliceBetween(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ int length = ctx.jsonProvider().length(model);
+ int from = operation.from();
+ int to = operation.to();
+
+ to = Math.min(length, to);
+
+ if (from >= to || length == 0) {
+ return;
+ }
+
+ logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString());
+
+ for (int i = from; i < to; i++) {
+ handleArrayIndex(i, currentPath, model, ctx);
+ }
+ }
+
+ private void sliceTo(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
+ int length = ctx.jsonProvider().length(model);
+ if (length == 0) {
+ return;
+ }
+ int to = operation.to();
+ if (to < 0) {
+ //calculate slice end from array length
+ to = length + to;
+ }
+ to = Math.min(length, to);
+
+ logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString());
+
+ for (int i = 0; i < to; i++) {
+ handleArrayIndex(i, currentPath, model, ctx);
+ }
+ }
+
+ @Override
+ public String getPathFragment() {
+ return operation.toString();
+ }
+
+ @Override
+ public boolean isTokenDefinite() {
+ return false;
+ }
+
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
index 018a8b5e0..80ac2bd4f 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
@@ -19,9 +19,13 @@
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.ParamType;
+import com.jayway.jsonpath.internal.function.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Arrays;
+
public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
@@ -32,7 +36,7 @@ public class CompiledPath implements Path {
public CompiledPath(RootPathToken root, boolean isRootPath) {
- this.root = root;
+ this.root = invertScannerFunctionRelationship(root);
this.isRootPath = isRootPath;
}
@@ -41,6 +45,48 @@ public boolean isRootPath() {
return isRootPath;
}
+
+
+ /**
+ * In the event the writer of the path referenced a function at the tail end of a scanner, augment the query such
+ * that the root node is the function and the parameter to the function is the scanner. This way we maintain
+ * relative sanity in the path expression, functions either evaluate scalar values or arrays, they're
+ * not re-entrant nor should they maintain state, they do however take parameters.
+ *
+ * @param path
+ * this is our old root path which will become a parameter (assuming there's a scanner terminated by a function
+ *
+ * @return
+ * A function with the scanner as input, or if this situation doesn't exist just the input path
+ */
+ private RootPathToken invertScannerFunctionRelationship(final RootPathToken path) {
+ if (path.isFunctionPath() && path.next() instanceof ScanPathToken) {
+ PathToken token = path;
+ PathToken prior = null;
+ while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) {
+ prior = token;
+ }
+ // Invert the relationship $..path.function() to $.function($..path)
+ if (token instanceof FunctionPathToken) {
+ prior.setNext(null);
+ path.setTail(prior);
+
+ // Now generate a new parameter from our path
+ Parameter parameter = new Parameter();
+ parameter.setPath(new CompiledPath(path, true));
+ parameter.setType(ParamType.PATH);
+ ((FunctionPathToken)token).setParameters(Arrays.asList(parameter));
+ RootPathToken functionRoot = new RootPathToken('$');
+ functionRoot.setTail(token);
+ functionRoot.setNext(token);
+
+ // Define the function as the root
+ return functionRoot;
+ }
+ }
+ return path;
+ }
+
@Override
public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate) {
if (logger.isDebugEnabled()) {
@@ -51,7 +97,7 @@ public EvaluationContext evaluate(Object document, Object rootDocument, Configur
try {
PathRef op = ctx.forUpdate() ? PathRef.createRoot(rootDocument) : PathRef.NO_OP;
root.evaluate("", op, document, ctx);
- } catch (EvaluationAbortException abort){};
+ } catch (EvaluationAbortException abort) {}
return ctx;
}
@@ -75,4 +121,8 @@ public boolean isFunctionPath() {
public String toString() {
return root.toString();
}
+
+ public RootPathToken getRoot() {
+ return root;
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
index 3fc7e4873..d3fd0b1c6 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/EvaluationContextImpl.java
@@ -48,9 +48,14 @@ public class EvaluationContextImpl implements EvaluationContext {
private final List updateOperations;
private final HashMap documentEvalCache = new HashMap();
private final boolean forUpdate;
+ private final boolean suppressExceptions;
private int resultIndex = 0;
+ public RootPathToken getRoot(){
+ return ((CompiledPath) path).getRoot();
+ }
+
public EvaluationContextImpl(Path path, Object rootDocument, Configuration configuration, boolean forUpdate) {
notNull(path, "path can not be null");
notNull(rootDocument, "root can not be null");
@@ -61,7 +66,8 @@ public EvaluationContextImpl(Path path, Object rootDocument, Configuration confi
this.configuration = configuration;
this.valueResult = configuration.jsonProvider().createArray();
this.pathResult = configuration.jsonProvider().createArray();
- this.updateOperations = new ArrayList();
+ this.updateOperations = new ArrayList<>();
+ this.suppressExceptions = configuration.containsOption(Option.SUPPRESS_EXCEPTIONS);
}
public HashMap documentEvalCache() {
@@ -129,7 +135,10 @@ public T getValue() {
@Override
public T getValue(boolean unwrap) {
if (path.isDefinite()) {
- if(resultIndex == 0){
+ if(resultIndex == 0) {
+ if (suppressExceptions) {
+ return null;
+ }
throw new PathNotFoundException("No results for path: " + path.toString());
}
int len = jsonProvider().length(valueResult);
@@ -145,7 +154,10 @@ public T getValue(boolean unwrap) {
@SuppressWarnings("unchecked")
@Override
public T getPath() {
- if(resultIndex == 0){
+ if(resultIndex == 0) {
+ if (suppressExceptions) {
+ return null;
+ }
throw new PathNotFoundException("No results for path: " + path.toString());
}
return (T)pathResult;
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
index b15638150..0f4498958 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
@@ -1,5 +1,6 @@
package com.jayway.jsonpath.internal.path;
+import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.function.Parameter;
import com.jayway.jsonpath.internal.function.PathFunction;
@@ -20,7 +21,7 @@ public class FunctionPathToken extends PathToken {
private final String functionName;
private final String pathFragment;
- private final List functionParams;
+ private List functionParams;
public FunctionPathToken(String pathFragment, List parameters) {
this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()");
@@ -39,26 +40,47 @@ public void evaluate(String currentPath, PathRef parent, Object model, Evaluatio
evaluateParameters(currentPath, parent, model, ctx);
Object result = pathFunction.invoke(currentPath, parent, model, ctx, functionParams);
ctx.addResult(currentPath + "." + functionName, parent, result);
+ cleanWildcardPathToken();
if (!isLeaf()) {
next().evaluate(currentPath, parent, result, ctx);
}
}
+ private void cleanWildcardPathToken() {
+ if (null != functionParams && functionParams.size() > 0) {
+ Path path = functionParams.get(0).getPath();
+ if (null != path && !path.isFunctionPath() && path instanceof CompiledPath) {
+ RootPathToken root = ((CompiledPath) path).getRoot();
+ PathToken tail = root.getNext();
+ while (null != tail && null != tail.getNext() ) {
+ if(tail.getNext() instanceof WildcardPathToken){
+ tail.setNext(tail.getNext().getNext());
+ break;
+ }
+ tail = tail.getNext();
+ }
+ }
+ }
+ }
+
private void evaluateParameters(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (null != functionParams) {
for (Parameter param : functionParams) {
- if (!param.hasEvaluated()) {
- switch (param.getType()) {
- case PATH:
- param.setLateBinding(new PathLateBindingValue(param.getPath(), ctx.rootDocument(), ctx.configuration()));
+ switch (param.getType()) {
+ case PATH:
+ PathLateBindingValue pathLateBindingValue = new PathLateBindingValue(param.getPath(), ctx.rootDocument(), ctx.configuration());
+ if (!param.hasEvaluated()||!pathLateBindingValue.equals(param.getILateBingValue())) {
+ param.setLateBinding(pathLateBindingValue);
param.setEvaluated(true);
- break;
- case JSON:
+ }
+ break;
+ case JSON:
+ if (!param.hasEvaluated()) {
param.setLateBinding(new JsonLateBindingValue(ctx.configuration().jsonProvider(), param));
param.setEvaluated(true);
- break;
- }
+ }
+ break;
}
}
}
@@ -68,7 +90,7 @@ private void evaluateParameters(String currentPath, PathRef parent, Object model
* Return the actual value by indicating true. If this return was false then we'd return the value in an array which
* isn't what is desired - true indicates the raw value is returned.
*
- * @return
+ * @return true if token is definite
*/
@Override
public boolean isTokenDefinite() {
@@ -81,4 +103,14 @@ public String getPathFragment() {
}
+ public void setParameters(List parameters) {
+ this.functionParams = parameters;
+ }
+
+ public List getParameters() {
+ return this.functionParams;
+ }
+ public String getFunctionName() {
+ return this.functionName;
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
index 0317311ec..27b6e0633 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
@@ -72,8 +72,7 @@ public static Path compile(String path, final Predicate... filters) {
fail("Path must not end with a '.' or '..'");
}
LinkedList filterStack = new LinkedList(asList(filters));
- Path p = new PathCompiler(ci, filterStack).compile();
- return p;
+ return new PathCompiler(ci, filterStack).compile();
} catch (Exception e) {
InvalidPathException ipe;
if (e instanceof InvalidPathException) {
@@ -117,7 +116,7 @@ private RootPathToken readContextToken() {
path.incrementPosition(1);
if(path.currentChar() != PERIOD && path.currentChar() != OPEN_SQUARE_BRACKET){
- fail("Illegal character at position " + path.position() + " expected '.' or '[");
+ fail("Illegal character at position " + path.position() + " expected '.' or '['");
}
PathTokenAppender appender = pathToken.getPathTokenAppender();
@@ -135,21 +134,26 @@ private boolean readNextToken(PathTokenAppender appender) {
switch (c) {
case OPEN_SQUARE_BRACKET:
- return readBracketPropertyToken(appender) ||
- readArrayToken(appender) ||
- readWildCardToken(appender) ||
- readFilterToken(appender) ||
- readPlaceholderToken(appender) ||
- fail("Could not parse token starting at position " + path.position() + ". Expected ?, ', 0-9, * ");
+ if (!readBracketPropertyToken(appender) && !readArrayToken(appender) && !readWildCardToken(appender)
+ && !readFilterToken(appender) && !readPlaceholderToken(appender)) {
+ fail("Could not parse token starting at position " + path.position() + ". Expected ?, ', 0-9, * ");
+ }
+ return true;
case PERIOD:
- return readDotToken(appender) ||
- fail("Could not parse token starting at position " + path.position());
+ if (!readDotToken(appender)) {
+ fail("Could not parse token starting at position " + path.position());
+ }
+ return true;
case WILDCARD:
- return readWildCardToken(appender) ||
- fail("Could not parse token starting at position " + path.position());
+ if (!readWildCardToken(appender)) {
+ fail("Could not parse token starting at position " + path.position());
+ }
+ return true;
default:
- return readPropertyOrFunctionToken(appender) ||
- fail("Could not parse token starting at position " + path.position());
+ if (!readPropertyOrFunctionToken(appender)) {
+ fail("Could not parse token starting at position " + path.position());
+ }
+ return true;
}
}
@@ -195,7 +199,7 @@ else if (c == PERIOD || c == OPEN_SQUARE_BRACKET) {
}
else if (c == OPEN_PARENTHESIS) {
isFunction = true;
- endPosition = readPosition++;
+ endPosition = readPosition;
break;
}
readPosition++;
@@ -207,6 +211,21 @@ else if (c == OPEN_PARENTHESIS) {
List functionParameters = null;
if (isFunction) {
+ int parenthesis_count = 1;
+ for(int i = readPosition + 1; i < path.length(); i++){
+ if (path.charAt(i) == CLOSE_PARENTHESIS)
+ parenthesis_count--;
+ else if (path.charAt(i) == OPEN_PARENTHESIS)
+ parenthesis_count++;
+ if (parenthesis_count == 0)
+ break;
+ }
+
+ if (parenthesis_count != 0){
+ String functionName = path.subSequence(startPosition, endPosition).toString();
+ throw new InvalidPathException("Arguments to function: '" + functionName + "' are not closed properly.");
+ }
+
if (path.inBounds(readPosition+1)) {
// read the next token to determine if we have a simple no-args function call
char c = path.charAt(readPosition + 1);
@@ -271,11 +290,11 @@ private List parseFunctionParameters(String funcName) {
// Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the
// last parameter boundary
- Integer groupParen = 1, groupBracket = 0, groupBrace = 0, groupQuote = 0;
- Boolean endOfStream = false;
+ int groupParen = 1, groupBracket = 0, groupBrace = 0, groupQuote = 0;
+ boolean endOfStream = false;
char priorChar = 0;
List parameters = new ArrayList();
- StringBuffer parameter = new StringBuffer();
+ StringBuilder parameter = new StringBuilder();
while (path.inBounds() && !endOfStream) {
char c = path.currentChar();
path.incrementPosition(1);
@@ -286,7 +305,7 @@ private List parseFunctionParameters(String funcName) {
continue;
}
- if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c) {
+ if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c || MINUS == c) {
type = ParamType.JSON;
}
else if (isPathContext(c)) {
@@ -297,9 +316,6 @@ else if (isPathContext(c)) {
switch (c) {
case DOUBLE_QUOTE:
if (priorChar != '\\' && groupQuote > 0) {
- if (groupQuote == 0) {
- throw new InvalidPathException("Unexpected quote '\"' at character position: " + path.position());
- }
groupQuote--;
}
else {
@@ -333,7 +349,8 @@ else if (isPathContext(c)) {
// we've encountered a COMMA do the same
case CLOSE_PARENTHESIS:
groupParen--;
- if (0 != groupParen) {
+ //CS304 Issue link: https://github.com/json-path/JsonPath/issues/620
+ if (0 > groupParen || priorChar == '(') {
parameter.append(c);
}
case COMMA:
@@ -351,7 +368,7 @@ else if (isPathContext(c)) {
param = new Parameter(parameter.toString());
break;
case PATH:
- LinkedList predicates = new LinkedList();
+ LinkedList predicates = new LinkedList<>();
PathCompiler compiler = new PathCompiler(parameter.toString(), predicates);
param = new Parameter(compiler.compile());
break;
@@ -415,7 +432,7 @@ private boolean readPlaceholderToken(PathTokenAppender appender) {
Collection predicates = new ArrayList();
for (String token : tokens) {
- token = token != null ? token.trim() : token;
+ token = token != null ? token.trim() : null;
if (!"?".equals(token == null ? "" : token)) {
throw new InvalidPathException("Expected '?' but found " + token);
}
@@ -484,7 +501,8 @@ private boolean readWildCardToken(PathTokenAppender appender) {
if (inBracket) {
int wildCardIndex = path.indexOfNextSignificantChar(WILDCARD);
if (!path.nextSignificantCharIs(wildCardIndex, CLOSE_SQUARE_BRACKET)) {
- throw new InvalidPathException("Expected wildcard token to end with ']' on position " + wildCardIndex + 1);
+ int offset = wildCardIndex + 1;
+ throw new InvalidPathException("Expected wildcard token to end with ']' on position " + offset);
}
int bracketCloseIndex = path.indexOfNextSignificantChar(wildCardIndex, CLOSE_SQUARE_BRACKET);
path.setPosition(bracketCloseIndex + 1);
@@ -580,7 +598,7 @@ private boolean readBracketPropertyToken(PathTokenAppender appender) {
}
break;
} else if (c == potentialStringDelimiter) {
- if (inProperty && !inEscape) {
+ if (inProperty) {
char nextSignificantChar = path.nextSignificantChar(readPosition);
if (nextSignificantChar != CLOSE_SQUARE_BRACKET && nextSignificantChar != COMMA) {
fail("Property must be separated by comma or Property must be terminated close square bracket at index "+readPosition);
@@ -594,7 +612,7 @@ private boolean readBracketPropertyToken(PathTokenAppender appender) {
inProperty = true;
lastSignificantWasComma = false;
}
- } else if (c == COMMA){
+ } else if (c == COMMA && !inProperty) {
if (lastSignificantWasComma){
fail("Found empty property at index "+readPosition);
}
@@ -603,7 +621,15 @@ private boolean readBracketPropertyToken(PathTokenAppender appender) {
readPosition++;
}
- int endBracketIndex = path.indexOfNextSignificantChar(endPosition, CLOSE_SQUARE_BRACKET) + 1;
+ if (inProperty){
+ fail("Property has not been closed - missing closing " + potentialStringDelimiter);
+ }
+
+ int endBracketIndex = path.indexOfNextSignificantChar(endPosition, CLOSE_SQUARE_BRACKET);
+ if(endBracketIndex == -1) {
+ fail("Property has not been closed - missing closing ]");
+ }
+ endBracketIndex++;
path.setPosition(endBracketIndex);
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
index e4d236043..34cf1402b 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
@@ -29,6 +29,12 @@ public abstract class PathToken {
private PathToken next;
private Boolean definite = null;
private Boolean upstreamDefinite = null;
+ private int upstreamArrayIndex = -1;
+
+
+ public void setUpstreamArrayIndex(int idx){
+ upstreamArrayIndex = idx;
+ }
PathToken appendTailToken(PathToken next) {
this.next = next;
@@ -75,7 +81,10 @@ void handleObjectProperty(String currentPath, Object model, EvaluationContextImp
}
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP;
if (isLeaf()) {
- ctx.addResult(evalPath, pathRef, propertyVal);
+ String idx = "[" + String.valueOf(upstreamArrayIndex) + "]";
+ if(idx.equals("[-1]") || ctx.getRoot().getTail().prev().getPathFragment().equals(idx)){
+ ctx.addResult(evalPath, pathRef, propertyVal);
+ }
}
else {
next().evaluate(evalPath, pathRef, propertyVal, ctx);
@@ -215,4 +224,11 @@ public void invoke(PathFunction pathFunction, String currentPath, PathRef parent
protected abstract String getPathFragment();
+ public void setNext(final PathToken next) {
+ this.next = next;
+ }
+
+ public PathToken getNext() {
+ return this.next;
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
index 995c73e40..b9dacb6d7 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
@@ -23,11 +23,11 @@ public static PathToken createPropertyPathToken(List properties, char st
}
public static PathToken createSliceArrayPathToken(final ArraySliceOperation arraySliceOperation) {
- return new ArrayPathToken(arraySliceOperation);
+ return new ArraySliceToken(arraySliceOperation);
}
public static PathToken createIndexArrayPathToken(final ArrayIndexOperation arrayIndexOperation) {
- return new ArrayPathToken(arrayIndexOperation);
+ return new ArrayIndexToken(arrayIndexOperation);
}
public static PathToken createWildCardPathToken() {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java
index af668634f..ee67fc1e3 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PredicatePathToken.java
@@ -20,6 +20,7 @@
import com.jayway.jsonpath.internal.PathRef;
import java.util.Collection;
+import java.util.Collections;
import static java.lang.String.format;
import static java.util.Arrays.asList;
@@ -33,7 +34,7 @@ public class PredicatePathToken extends PathToken {
private final Collection predicates;
PredicatePathToken(Predicate filter) {
- this.predicates = asList(filter);
+ this.predicates = Collections.singletonList(filter);
}
PredicatePathToken(Collection predicates) {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PropertyPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PropertyPathToken.java
index 74d280e49..79df70e6e 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PropertyPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PropertyPathToken.java
@@ -15,6 +15,7 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.InvalidPathException;
+import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils;
@@ -27,7 +28,7 @@
/**
*
*/
-class PropertyPathToken extends PathToken {
+public class PropertyPathToken extends PathToken {
private final List properties;
private final String stringDelimiter;
@@ -54,7 +55,7 @@ public boolean multiPropertyMergeCase() {
public boolean multiPropertyIterationCase() {
// Semantics of this case is the same as semantics of ArrayPathToken with INDEX_SEQUENCE operation.
- return ! isLeaf() && properties.size() > 1;
+ return !isLeaf() && properties.size() > 1;
}
@Override
@@ -63,14 +64,14 @@ public void evaluate(String currentPath, PathRef parent, Object model, Evaluatio
assert onlyOneIsTrueNonThrow(singlePropertyCase(), multiPropertyMergeCase(), multiPropertyIterationCase());
if (!ctx.jsonProvider().isMap(model)) {
- if (! isUpstreamDefinite()) {
+ if (!isUpstreamDefinite()
+ || ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)) {
return;
} else {
String m = model == null ? "null" : model.getClass().getName();
-
throw new PathNotFoundException(String.format(
"Expected to find an object with property %s in path %s but found '%s'. " +
- "This is not a json object according to the JsonProvider: '%s'.",
+ "This is not a json object according to the JsonProvider: '%s'.",
getPathFragment(), currentPath, m, ctx.configuration().jsonProvider().getClass().getName()));
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
index 2b2db7b18..1a786ac64 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
@@ -27,11 +27,15 @@ public class RootPathToken extends PathToken {
RootPathToken(char rootToken) {
- this.rootToken = Character.toString(rootToken);;
+ this.rootToken = Character.toString(rootToken);
this.tail = this;
this.tokenCount = 1;
}
+ public PathToken getTail(){
+ return this.tail;
+ }
+
@Override
public int getTokenCount() {
return tokenCount;
@@ -76,4 +80,8 @@ public boolean isTokenDefinite() {
public boolean isFunctionPath() {
return (tail instanceof FunctionPathToken);
}
+
+ public void setTail(PathToken token) {
+ this.tail = token;
+ }
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ScanPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ScanPathToken.java
index f98c3729a..9d6da973c 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ScanPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ScanPathToken.java
@@ -55,6 +55,7 @@ public static void walkArray(PathToken pt, String currentPath, PathRef parent, O
int idx = 0;
for (Object evalModel : models) {
String evalPath = currentPath + "[" + idx + "]";
+ next.setUpstreamArrayIndex(idx);
next.evaluate(evalPath, parent, evalModel, ctx);
idx++;
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java
index 37d02ec98..455cea1f8 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java
@@ -14,6 +14,8 @@
*/
package com.jayway.jsonpath.internal.path;
+import java.util.Collections;
+
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef;
@@ -25,14 +27,14 @@
*/
public class WildcardPathToken extends PathToken {
- WildcardPathToken() {
+ public WildcardPathToken() {
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) {
for (String property : ctx.jsonProvider().getPropertyKeys(model)) {
- handleObjectProperty(currentPath, model, ctx, asList(property));
+ handleObjectProperty(currentPath, model, ctx, Collections.singletonList(property));
}
} else if (ctx.jsonProvider().isArray(model)) {
for (int idx = 0; idx < ctx.jsonProvider().length(model); idx++) {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java
index bef6e7c2b..6853728e3 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java
@@ -10,14 +10,13 @@ public interface Cache {
* @param key cache key to lookup the JsonPath
* @return JsonPath
*/
- public JsonPath get(String key);
+ JsonPath get(String key);
/**
* Add JsonPath to the cache
* @param key cache key to store the JsonPath
* @param value JsonPath to be cached
- * @return void
* @throws InvalidJsonException
*/
- public void put(String key, JsonPath value);
+ void put(String key, JsonPath value);
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java
index 82d6f18f7..f90cb94a3 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java
@@ -2,33 +2,47 @@
import com.jayway.jsonpath.JsonPathException;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
import static com.jayway.jsonpath.internal.Utils.notNull;
public class CacheProvider {
- private static Cache cache;
- private static boolean cachingEnabled;
+
+ private static final AtomicReferenceFieldUpdater UPDATER =
+ AtomicReferenceFieldUpdater.newUpdater(CacheProvider.class, Cache.class, "cache");
+ private static final CacheProvider instance = new CacheProvider();
+
+ private volatile Cache cache;
+
+ private static class CacheHolder {
+ static final Cache CACHE;
+ static {
+ Cache cache = CacheProvider.instance.cache;
+ // the application is trying to use the cache
+ // and if no external implementation has been registered,
+ // we need to initialise it to the default LRUCache
+ if (cache == null) {
+ cache = getDefaultCache();
+ // on the off chance that the cache implementation was registered during
+ // initialisation of the holder, this should be respected, so if the
+ // default cache can't be written back, just read the user supplied value again
+ if (!UPDATER.compareAndSet(instance, null, cache)) {
+ cache = CacheProvider.instance.cache;
+ }
+ }
+ CACHE = cache;
+ }
+ }
public static void setCache(Cache cache){
notNull(cache, "Cache may not be null");
- synchronized (CacheProvider.class){
- if(CacheProvider.cache != null){
- throw new JsonPathException("Cache provider must be configured before cache is accessed.");
- } else {
- CacheProvider.cache = cache;
- }
- cachingEnabled = !(CacheProvider.cache instanceof NOOPCache);
+ if (!UPDATER.compareAndSet(instance, null, cache)) {
+ throw new JsonPathException("Cache provider must be configured before cache is accessed and must not be registered twice.");
}
}
public static Cache getCache() {
- if(CacheProvider.cache == null){
- synchronized (CacheProvider.class){
- if(CacheProvider.cache == null){
- CacheProvider.cache = getDefaultCache();
- }
- }
- }
- return CacheProvider.cache;
+ return CacheHolder.CACHE;
}
@@ -36,4 +50,4 @@ private static Cache getDefaultCache(){
return new LRUCache(400);
//return new NOOPCache();
}
-}
\ No newline at end of file
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java
index 56cfb47c1..97b139dd9 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java
@@ -43,6 +43,7 @@ public Object getArrayIndex(Object obj, int idx) {
return ((List) obj).get(idx);
}
+ @Deprecated
public final Object getArrayIndex(Object obj, int idx, boolean unwrap){
return getArrayIndex(obj, idx);
}
@@ -152,7 +153,8 @@ public int length(Object obj) {
} else if(obj instanceof String){
return ((String)obj).length();
}
- throw new JsonPathException("length operation cannot be applied to " + obj!=null?obj.getClass().getName():"null");
+ throw new JsonPathException("length operation cannot be applied to " + (obj != null ? obj.getClass().getName()
+ : "null"));
}
/**
@@ -162,7 +164,7 @@ public int length(Object obj) {
* @return an Iterable that iterates over the entries of an array
*/
@SuppressWarnings("unchecked")
- public Iterable extends Object> toIterable(Object obj) {
+ public Iterable> toIterable(Object obj) {
if (isArray(obj))
return ((Iterable) obj);
else
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
index 0828b2951..d28e7cc79 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java
@@ -85,6 +85,7 @@ public Object unwrap(final Object o) {
private static boolean isPrimitiveNumber(final Number n) {
return n instanceof Integer ||
+ n instanceof Float ||
n instanceof Double ||
n instanceof Long ||
n instanceof BigDecimal ||
@@ -97,9 +98,9 @@ private static Number unwrapNumber(final Number n) {
if (!isPrimitiveNumber(n)) {
BigDecimal bigDecimal = new BigDecimal(n.toString());
if (bigDecimal.scale() <= 0) {
- if (bigDecimal.compareTo(new BigDecimal(Integer.MAX_VALUE)) <= 0) {
+ if (bigDecimal.abs().compareTo(new BigDecimal(Integer.MAX_VALUE)) <= 0) {
unwrapped = bigDecimal.intValue();
- } else if (bigDecimal.compareTo(new BigDecimal(Long.MAX_VALUE)) <= 0){
+ } else if (bigDecimal.abs().compareTo(new BigDecimal(Long.MAX_VALUE)) <= 0){
unwrapped = bigDecimal.longValue();
} else {
unwrapped = bigDecimal;
@@ -246,9 +247,8 @@ public int length(final Object obj) {
}
}
}
-
- throw new JsonPathException("length operation can not applied to " + obj != null ? obj.getClass().getName()
- : "null");
+ throw new JsonPathException("length operation can not applied to " + (obj != null ? obj.getClass().getName()
+ : "null"));
}
@Override
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonNodeJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonNodeJsonProvider.java
index 8e525c083..ae1659277 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonNodeJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonNodeJsonProvider.java
@@ -8,16 +8,18 @@
import com.fasterxml.jackson.databind.node.TextNode;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPathException;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+
public class JacksonJsonNodeJsonProvider extends AbstractJsonProvider {
private static final ObjectMapper defaultObjectMapper = new ObjectMapper();
@@ -29,14 +31,14 @@ public ObjectMapper getObjectMapper() {
}
/**
- * Initialize the JacksonTreeJsonProvider with the default ObjectMapper and ObjectReader
+ * Initialize the JacksonJsonNodeJsonProvider with the default ObjectMapper and ObjectReader
*/
public JacksonJsonNodeJsonProvider() {
this(defaultObjectMapper);
}
/**
- * Initialize the JacksonTreeJsonProvider with a custom ObjectMapper and ObjectReader.
+ * Initialize the JacksonJsonNodeJsonProvider with a custom ObjectMapper and ObjectReader.
*
* @param objectMapper the ObjectMapper to use
*/
@@ -53,6 +55,16 @@ public Object parse(String json) throws InvalidJsonException {
}
}
+ @Override
+ public Object parse(byte[] json)
+ throws InvalidJsonException {
+ try {
+ return objectMapper.readTree(json);
+ } catch (IOException e) {
+ throw new InvalidJsonException(e, new String(json, StandardCharsets.UTF_8));
+ }
+ }
+
@Override
public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
try {
@@ -81,7 +93,6 @@ public Object createMap() {
}
public Object unwrap(Object o) {
-
if (o == null) {
return null;
}
@@ -101,8 +112,8 @@ public Object unwrap(Object o) {
return e.asInt();
} else if (e.isLong()) {
return e.asLong();
- } else if (e.isBigDecimal()) {
- return e.decimalValue();
+ } else if (e.isBigInteger()) {
+ return e.bigIntegerValue();
} else if (e.isDouble()) {
return e.doubleValue();
} else if (e.isFloat()) {
@@ -148,7 +159,7 @@ public Object getMapValue(Object obj, String key) {
if (!jsonObject.has(key)) {
return UNDEFINED;
} else {
- return unwrap(o);
+ return o;
}
}
@@ -174,9 +185,9 @@ public void setProperty(Object obj, Object key, Object value) {
}
public void removeProperty(Object obj, Object key) {
- if (isMap(obj))
+ if (isMap(obj)) {
toJsonObject(obj).remove(key.toString());
- else {
+ } else {
ArrayNode array = toJsonArray(obj);
int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString());
array.remove(index);
@@ -211,17 +222,30 @@ public int length(Object obj) {
return element.size();
}
}
- throw new JsonPathException("length operation can not applied to " + obj != null ? obj.getClass().getName() : "null");
+ throw new JsonPathException("length operation can not applied to " + (obj != null ? obj.getClass().getName()
+ : "null"));
}
@Override
public Iterable> toIterable(Object obj) {
ArrayNode arr = toJsonArray(obj);
- List values = new ArrayList(arr.size());
- for (Object o : arr) {
- values.add(unwrap(o));
- }
- return values;
+ Iterator> iterator = arr.iterator();
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ return unwrap(iterator.next());
+ }
+ };
+ }
+ };
}
private JsonNode createJsonElement(Object o) {
@@ -257,7 +281,9 @@ private void setValueInObjectNode(ObjectNode objectNode, Object key, Object valu
objectNode.put(key.toString(), (Long) value);
} else if (value instanceof Short) {
objectNode.put(key.toString(), (Short) value);
- } else if (value instanceof Double) {
+ } else if (value instanceof BigInteger) {
+ objectNode.put(key.toString(), (BigInteger) value);
+ } else if (value instanceof Double) {
objectNode.put(key.toString(), (Double) value);
} else if (value instanceof Float) {
objectNode.put(key.toString(), (Float) value);
@@ -270,7 +296,7 @@ private void setValueInObjectNode(ObjectNode objectNode, Object key, Object valu
} else if (value == null) {
objectNode.set(key.toString(), null); // this will create a null-node
} else {
- objectNode.put(key.toString(), createJsonElement(value));
+ objectNode.set(key.toString(), createJsonElement(value));
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonProvider.java
index ca60048cd..32522f55c 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JacksonJsonProvider.java
@@ -18,19 +18,20 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.jayway.jsonpath.InvalidJsonException;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+
public class JacksonJsonProvider extends AbstractJsonProvider {
private static final ObjectMapper defaultObjectMapper = new ObjectMapper();
- private static final ObjectReader defaultObjectReader = defaultObjectMapper.reader().withType(Object.class);
+ private static final ObjectReader defaultObjectReader = defaultObjectMapper.reader().forType(Object.class);
protected ObjectMapper objectMapper;
protected ObjectReader objectReader;
@@ -51,7 +52,7 @@ public JacksonJsonProvider() {
* @param objectMapper the ObjectMapper to use
*/
public JacksonJsonProvider(ObjectMapper objectMapper) {
- this(objectMapper, objectMapper.reader().withType(Object.class));
+ this(objectMapper, objectMapper.reader().forType(Object.class));
}
/**
@@ -73,6 +74,16 @@ public Object parse(String json) throws InvalidJsonException {
}
}
+ @Override
+ public Object parse(byte[] json)
+ throws InvalidJsonException {
+ try {
+ return objectReader.readValue(json);
+ } catch (IOException e) {
+ throw new InvalidJsonException(e, new String(json, StandardCharsets.UTF_8));
+ }
+ }
+
@Override
public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
try {
@@ -93,7 +104,7 @@ public String toJson(Object obj) {
generator.close();
return writer.getBuffer().toString();
} catch (IOException e) {
- throw new InvalidJsonException();
+ throw new InvalidJsonException(e);
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JakartaJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JakartaJsonProvider.java
new file mode 100644
index 000000000..0f9abf3e9
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JakartaJsonProvider.java
@@ -0,0 +1,1043 @@
+package com.jayway.jsonpath.spi.json;
+
+import com.jayway.jsonpath.InvalidJsonException;
+import com.jayway.jsonpath.JsonPathException;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonString;
+import jakarta.json.JsonStructure;
+import jakarta.json.JsonValue;
+import jakarta.json.spi.JsonProvider;
+import jakarta.json.stream.JsonParsingException;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+
+public class JakartaJsonProvider extends AbstractJsonProvider {
+
+ private static final JsonProvider defaultJsonProvider = JsonProvider.provider();
+ private static final JsonBuilderFactory jsonBuilderFactory = defaultJsonProvider.createBuilderFactory(null);
+
+ private final boolean mutableJson;
+
+ /**
+ * Constructs new instance of parsing and serialization adapter for Jakarta EE 9
+ * JSON-P default provider. JSON files, strings, and streams can be loaded, parsed,
+ * and navigated with JsonPath expressions, and values retrieved - but no changes
+ * to the loaded JSON document are permitted, and will yield exceptions.
+ */
+ public JakartaJsonProvider() {
+ this.mutableJson = false;
+ }
+
+ /**
+ * Constructs new instance of parsing and serialization adapter for Jakarta EE 9
+ * JSON-P default provider, and optionally enables proxying of {@code JsonObject}
+ * and {@code JsonArray} entities to implement mutable JSON structures. By default,
+ * all structures and values produced and consumed by JSON-P are immutable. This
+ * comes at an extra cost to perfomance and memory consumption, so enable only if
+ * expected use cases include add/put/replace/delete operations on JSON document.
+ *
+ * @param mutableJson enable dynamic proxies for JSON structures
+ */
+ public JakartaJsonProvider(boolean mutableJson) {
+ this.mutableJson = mutableJson;
+ }
+
+ @Override
+ public Object parse(String json) throws InvalidJsonException {
+ return parse(new StringReader(json));
+ }
+
+ @Override
+ public Object parse(byte[] json)
+ throws InvalidJsonException {
+ return parse(new InputStreamReader(new ByteArrayInputStream(json), StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
+ try {
+ return parse(new InputStreamReader(jsonStream, charset));
+ } catch (UnsupportedEncodingException e) {
+ throw new JsonPathException(e);
+ }
+ }
+
+ private Object parse(Reader jsonInput) {
+ try (JsonReader jsonReader = defaultJsonProvider.createReader(jsonInput)) {
+ JsonStructure jsonStruct = jsonReader.read();
+ return mutableJson ? proxyAll(jsonStruct) : jsonStruct;
+ } catch (JsonParsingException e) {
+ throw new InvalidJsonException(e);
+ }
+ // not catching a JsonException as it never happens here
+ }
+
+ @Override
+ public String toJson(Object obj) {
+ if (obj instanceof JsonObjectBuilder) {
+ obj = ((JsonObjectBuilder) obj).build();
+ } else if (obj instanceof JsonArrayBuilder) {
+ obj = ((JsonArrayBuilder) obj).build();
+ } else if (obj instanceof List) {
+ obj = jsonBuilderFactory.createArrayBuilder((Collection>) obj).build();
+ }
+ return obj.toString();
+ }
+
+ @Override
+ public Object createArray() {
+ if (mutableJson) {
+ return new JsonArrayProxy(jsonBuilderFactory.createArrayBuilder().build());
+ } else {
+ return new LinkedList();
+ }
+ }
+
+ @Override
+ public Object createMap() {
+ if (mutableJson) {
+ return new JsonObjectProxy(jsonBuilderFactory.createObjectBuilder().build());
+ } else {
+ return jsonBuilderFactory.createObjectBuilder();
+ }
+ }
+
+ @Override
+ public boolean isArray(Object obj) {
+ return (obj instanceof JsonArray || obj instanceof JsonArrayBuilder || obj instanceof List);
+ }
+
+ @Override
+ public Object getArrayIndex(Object obj, int idx) {
+ if (obj instanceof JsonArrayBuilder) {
+ obj = ((JsonArrayBuilder) obj).build();
+ }
+ if (obj instanceof JsonArray) {
+ return ((JsonArray) obj).get(idx);
+ } else if (obj instanceof List>) {
+ return super.getArrayIndex(obj, idx);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public void setArrayIndex(Object array, int index, Object newValue) {
+ if (array instanceof JsonArrayBuilder) {
+ // next line is not optimal, but ArrayBuilder has no size() method
+ if (index == ((JsonArrayBuilder) array).build().size()) {
+ array = ((JsonArrayBuilder) array).add(wrap(newValue));
+ } else {
+ array = ((JsonArrayBuilder) array).set(index, wrap(newValue));
+ }
+ } else if (array instanceof JsonArray) {
+ if (mutableJson && array instanceof JsonArrayProxy) {
+ ((JsonArrayProxy) array).set(index, wrap(newValue));
+ } else {
+ throw new UnsupportedOperationException("JsonArray is immutable in JSON-P");
+ }
+ } else {
+ super.setArrayIndex(array, index, wrap(newValue));
+ }
+ }
+
+ @Override
+ public Object getMapValue(Object obj, String key) {
+ if (obj instanceof JsonObjectBuilder) {
+ obj = ((JsonObjectBuilder) obj).build();
+ }
+ if (obj instanceof JsonObject) {
+ JsonValue o = ((JsonObject) obj).get(key);
+ if (o == null) {
+ return UNDEFINED;
+ } else {
+ return unwrap(o);
+ }
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public void setProperty(Object obj, Object key, Object value) {
+ if (obj instanceof JsonObjectBuilder) {
+ ((JsonObjectBuilder) obj).add(key.toString(), wrap(value));
+ } else if (mutableJson && obj instanceof JsonObjectProxy) {
+ ((JsonObjectProxy) obj).put(key.toString(), wrap(value));
+ } else if (obj instanceof JsonObject) {
+ throw new UnsupportedOperationException("JsonObject is immutable in JSON-P");
+ } else if (obj instanceof JsonArrayBuilder) {
+ if (key == null) {
+ ((JsonArrayBuilder) obj).add(wrap(value));
+ } else {
+ ((JsonArrayBuilder) obj).set(toArrayIndex(key), wrap(value));
+ }
+ } else if (mutableJson && obj instanceof JsonArrayProxy) {
+ if (key == null) {
+ ((JsonArrayProxy) obj).add(wrap(value));
+ } else {
+ ((JsonArrayProxy) obj).set(toArrayIndex(key), wrap(value));
+ }
+ } else if (obj instanceof JsonArray) {
+ throw new UnsupportedOperationException("JsonArray is immutable in JSON-P");
+ } else if (obj instanceof List) {
+ @SuppressWarnings("unchecked")
+ List array = (List) obj;
+ if (key == null) {
+ array.add(wrap(value));
+ } else {
+ array.add(toArrayIndex(key), wrap(value));
+ }
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void removeProperty(Object obj, Object key) {
+ if (obj instanceof JsonObjectBuilder) {
+ ((JsonObjectBuilder) obj).remove(key.toString());
+ } else if (obj instanceof JsonObject) {
+ if (mutableJson && obj instanceof JsonObjectProxy) {
+ ((JsonObjectProxy) obj).remove(key);
+ } else {
+ throw new UnsupportedOperationException("JsonObject is immutable in JSON-P");
+ }
+ } else if (isArray(obj)) {
+ int index = toArrayIndex(key).intValue();
+ if (obj instanceof JsonArrayBuilder) {
+ ((JsonArrayBuilder) obj).remove(index);
+ } else if (obj instanceof List) {
+ // this also covers JsonArray as it implements List<>
+ ((List) obj).remove(index);
+ }
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public boolean isMap(Object obj) {
+ return (obj instanceof JsonObject || obj instanceof JsonObjectBuilder);
+ }
+
+ @Override
+ public Collection getPropertyKeys(Object obj) {
+ Set keys;
+ if (obj instanceof JsonObjectBuilder) {
+ keys = ((JsonObjectBuilder) obj).build().keySet();
+ } else if (obj instanceof JsonObject) {
+ keys = ((JsonObject) obj).keySet();
+ } else {
+ throw new UnsupportedOperationException("Json object is expected");
+ }
+ return new ArrayList(keys);
+ }
+
+ @Override
+ public int length(Object obj) {
+ if (isArray(obj)) {
+ if (obj instanceof JsonArrayBuilder) {
+ return ((JsonArrayBuilder) obj).build().size();
+ } else {
+ return ((List>) obj).size();
+ }
+ } else if (isMap(obj)) {
+ if (obj instanceof JsonObjectBuilder) {
+ obj = ((JsonObjectBuilder) obj).build();
+ }
+ return ((JsonObject) obj).size();
+ } else {
+ if (obj instanceof CharSequence) {
+ return ((CharSequence) obj).length();
+ }
+ }
+ String className = obj != null ? obj.getClass().getName() : null;
+ throw new JsonPathException("length operation can not applied to " + className);
+ }
+
+ @Override
+ public Iterable> toIterable(Object obj) {
+ List values;
+ if (isArray(obj)) {
+ if (obj instanceof JsonArrayBuilder) {
+ obj = ((JsonArrayBuilder) obj).build();
+ }
+ values = new ArrayList(((List>) obj).size());
+ for (Object val : ((List>) obj)) {
+ values.add(unwrap(val));
+ }
+ } else if (isMap(obj)) {
+ if (obj instanceof JsonObjectBuilder) {
+ obj = ((JsonObjectBuilder) obj).build();
+ }
+ values = new ArrayList(((JsonObject) obj).size());
+ for (JsonValue val : ((JsonObject) obj).values()) {
+ values.add(unwrap(val));
+ }
+ } else {
+ throw new UnsupportedOperationException("an array or object instance is expected");
+ }
+ return values;
+ }
+
+ @Override
+ public Object unwrap(Object obj) {
+ if (obj == null) {
+ return null;
+ }
+ if (!(obj instanceof JsonValue)) {
+ return obj;
+ }
+ switch (((JsonValue) obj).getValueType()) {
+ case ARRAY:
+ if (mutableJson && obj instanceof JsonArrayProxy) {
+ return (JsonArray) obj;
+ } else {
+ return ((JsonArray) obj).getValuesAs((JsonValue v) -> unwrap(v));
+ }
+ case STRING:
+ return ((JsonString) obj).getString();
+ case NUMBER:
+ if (((JsonNumber) obj).isIntegral()) {
+ //return ((JsonNumber) obj).bigIntegerValueExact();
+ try {
+ return ((JsonNumber) obj).intValueExact();
+ } catch (ArithmeticException e) {
+ return ((JsonNumber) obj).longValueExact();
+ }
+ } else {
+ //return ((JsonNumber) obj).bigDecimalValue();
+ return ((JsonNumber) obj).doubleValue();
+ }
+ case TRUE:
+ return Boolean.TRUE;
+ case FALSE:
+ return Boolean.FALSE;
+ case NULL:
+ return null;
+ default:
+ return obj;
+ }
+ }
+
+ private Integer toArrayIndex(Object index) {
+ try {
+ if (index instanceof Integer) {
+ return (Integer) index;
+ } else if (index instanceof Long) {
+ return Integer.valueOf(((Long) index).intValue());
+ } else if (index != null) {
+ return Integer.valueOf(index.toString());
+ } else {
+ //return null;
+ throw new IllegalArgumentException("Invalid array index");
+ }
+ } catch (NumberFormatException e) {
+ throw new JsonPathException(e);
+ }
+ }
+
+ private JsonValue wrap(Object obj) {
+ if (obj == null) {
+ return JsonValue.NULL;
+ } else if (obj instanceof JsonArray) {
+ if (!mutableJson || obj instanceof JsonArrayProxy) {
+ return (JsonArray) obj;
+ } else {
+ return proxyAll((JsonArray) obj);
+ }
+ } else if (obj instanceof JsonObject) {
+ if (!mutableJson || obj instanceof JsonObjectProxy) {
+ return (JsonObject) obj;
+ } else {
+ return proxyAll((JsonObject) obj);
+ }
+ } else if (obj instanceof JsonValue) {
+ return (JsonValue) obj;
+ } else if (Boolean.TRUE.equals(obj)) {
+ return JsonValue.TRUE;
+ } else if (Boolean.FALSE.equals(obj)) {
+ return JsonValue.FALSE;
+ } else if (obj instanceof CharSequence) {
+ return defaultJsonProvider.createValue(obj.toString());
+ } else if (obj instanceof Number) {
+ if (obj instanceof Integer) {
+ int v = ((Number) obj).intValue();
+ return defaultJsonProvider.createValue(v);
+ } else if (obj instanceof Long) {
+ long v = ((Number) obj).longValue();
+ return defaultJsonProvider.createValue(v);
+ } else if ((obj instanceof Float) || (obj instanceof Double)) {
+ double v = ((Number) obj).doubleValue();
+ return defaultJsonProvider.createValue(v);
+ } else if (obj instanceof BigInteger) {
+ return defaultJsonProvider.createValue((BigInteger) obj);
+ } else if (obj instanceof BigDecimal) {
+ return defaultJsonProvider.createValue((BigDecimal) obj);
+ } else {
+ // default to BigDecimal conversion for other numeric types
+ BigDecimal v = BigDecimal.valueOf(((Number) obj).doubleValue());
+ return defaultJsonProvider.createValue(v);
+ }
+ } else if (obj instanceof Collection) {
+ JsonArray result = jsonBuilderFactory.createArrayBuilder((Collection>) obj).build();
+ return mutableJson ? proxyAll(result) : result;
+ } else if (obj instanceof Map) {
+ @SuppressWarnings("unchecked")
+ Map map = (Map) obj;
+ JsonObject result = jsonBuilderFactory.createObjectBuilder(map).build();
+ return mutableJson ? proxyAll(result) : result;
+ } else if (obj instanceof JsonArrayBuilder) {
+ JsonArray result = ((JsonArrayBuilder) obj).build();
+ return mutableJson ? proxyAll(result) : result;
+ } else if (obj instanceof JsonObjectBuilder) {
+ JsonObject result = ((JsonObjectBuilder) obj).build();
+ return mutableJson ? proxyAll(result) : result;
+ } else {
+ String className = obj.getClass().getSimpleName();
+ throw new UnsupportedOperationException("Cannot create JSON element from " + className);
+ }
+ }
+
+ private JsonStructure proxyAll(JsonStructure jsonStruct) {
+ if (jsonStruct == null) {
+ return null;
+ } else if (jsonStruct instanceof JsonArrayProxy) {
+ return (JsonArray) jsonStruct;
+ } else if (jsonStruct instanceof JsonArray) {
+ List array = new ArrayList<>();
+ for (JsonValue v : (JsonArray) jsonStruct) {
+ if (v instanceof JsonStructure) {
+ v = proxyAll((JsonStructure) v);
+ }
+ array.add(v);
+ }
+ return new JsonArrayProxy(jsonBuilderFactory.createArrayBuilder(array).build());
+ } else if (jsonStruct instanceof JsonObjectProxy) {
+ return (JsonObject) jsonStruct;
+ } else if (jsonStruct instanceof JsonObject) {
+ Map map = new LinkedHashMap<>();
+ for (Map.Entry e : ((JsonObject) jsonStruct).entrySet()) {
+ JsonValue v = e.getValue();
+ if (v instanceof JsonStructure) {
+ v = proxyAll((JsonStructure) v);
+ }
+ map.put(e.getKey(), v);
+ }
+ return new JsonObjectProxy(jsonBuilderFactory.createObjectBuilder(map).build());
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private static class JsonArrayProxy implements JsonArray {
+
+ private JsonArray arr;
+
+ JsonArrayProxy(JsonArray arr) {
+ this.arr = arr;
+ }
+
+ @Override
+ public JsonObject getJsonObject(int index) {
+ return arr.getJsonObject(index);
+ }
+
+ @Override
+ public JsonArray getJsonArray(int index) {
+ return arr.getJsonArray(index);
+ }
+
+ @Override
+ public JsonNumber getJsonNumber(int index) {
+ return arr.getJsonNumber(index);
+ }
+
+ @Override
+ public JsonString getJsonString(int index) {
+ return arr.getJsonString(index);
+ }
+
+ @Override
+ public List getValuesAs(Class clazz) {
+ return arr.getValuesAs(clazz);
+ }
+
+ @Override
+ public String getString(int index) {
+ return arr.getString(index);
+ }
+
+ @Override
+ public String getString(int index, String defaultValue) {
+ return arr.getString(index, defaultValue);
+ }
+
+ @Override
+ public int getInt(int index) {
+ return arr.getInt(index);
+ }
+
+ @Override
+ public int getInt(int index, int defaultValue) {
+ return arr.getInt(index, defaultValue);
+ }
+
+ @Override
+ public boolean getBoolean(int index) {
+ return arr.getBoolean(index);
+ }
+
+ @Override
+ public boolean getBoolean(int index, boolean defaultValue) {
+ return arr.getBoolean(index, defaultValue);
+ }
+
+ @Override
+ public boolean isNull(int index) {
+ return arr.isNull(index);
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return arr.getValueType();
+ }
+
+ @Override
+ public int size() {
+ return arr.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return arr.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return arr.contains(o);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+
+ final JsonArray refArr = arr;
+ final Iterator it = arr.iterator();
+
+ @Override
+ public boolean hasNext() {
+ if (refArr == arr) {
+ return it.hasNext();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public JsonValue next() {
+ if (refArr == arr) {
+ return it.next();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+ };
+ }
+
+ @Override
+ public Object[] toArray() {
+ return arr.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return arr.toArray(a);
+ }
+
+ @Override
+ public boolean add(JsonValue e) {
+ arr = jsonBuilderFactory.createArrayBuilder(arr).add(e).build();
+ return true;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ int i = arr.indexOf(o);
+ if (i != -1) {
+ arr = jsonBuilderFactory.createArrayBuilder(arr).remove(i).build();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ return arr.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection extends JsonValue> c) {
+ if (!c.isEmpty()) {
+ JsonArrayBuilder builder = jsonBuilderFactory.createArrayBuilder(arr);
+ for (JsonValue v : c) {
+ builder.add(v);
+ }
+ arr = builder.build();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean addAll(int index, Collection extends JsonValue> c) {
+ if (c.isEmpty()) {
+ return false;
+ }
+ if (index < 0 || index >= arr.size()) {
+ throw new IndexOutOfBoundsException();
+ }
+ JsonArrayBuilder builder = jsonBuilderFactory.createArrayBuilder(arr);
+ for (int i = 0; i < arr.size(); i++) {
+ if (index == i) {
+ for (JsonValue v : c) {
+ builder.add(v);
+ }
+ }
+ builder.add(arr.get(i));
+ }
+ arr = builder.build();
+ return true;
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ if (c.isEmpty()) {
+ return false;
+ }
+ JsonArrayBuilder builder = null;
+ for (int i = 0, j = 0; i < arr.size(); i++, j++) {
+ if (c.contains(arr.get(i))) {
+ if (builder == null) {
+ builder = jsonBuilderFactory.createArrayBuilder(arr);
+ }
+ builder.remove(j--);
+ }
+ }
+ if (builder != null) {
+ arr = builder.build();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ if (c.isEmpty()) {
+ arr = jsonBuilderFactory.createArrayBuilder().build();
+ return true;
+ }
+ JsonArrayBuilder builder = null;
+ for (int i = 0, j = 0; i < arr.size(); i++, j++) {
+ if (!c.contains(arr.get(i))) {
+ if (builder == null) {
+ builder = jsonBuilderFactory.createArrayBuilder(arr);
+ }
+ builder.remove(j--);
+ }
+ }
+ if (builder != null) {
+ arr = builder.build();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void clear() {
+ arr = jsonBuilderFactory.createArrayBuilder().build();
+ }
+
+ @Override
+ public JsonValue get(int index) {
+ return arr.get(index);
+ }
+
+ @Override
+ public JsonValue set(int index, JsonValue element) {
+ if (index == arr.size()) {
+ arr = jsonBuilderFactory.createArrayBuilder(arr).add(index, element).build();
+ return null;
+ } else {
+ JsonValue oldValue = arr.get(index);
+ arr = jsonBuilderFactory.createArrayBuilder(arr).set(index, element).build();
+ return oldValue;
+ }
+ }
+
+ @Override
+ public void add(int index, JsonValue element) {
+ arr = jsonBuilderFactory.createArrayBuilder(arr).add(index, element).build();
+ }
+
+ @Override
+ public JsonValue remove(int index) {
+ JsonValue oldValue = arr.get(index);
+ arr = jsonBuilderFactory.createArrayBuilder(arr).remove(index).build();
+ return oldValue;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return arr.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return arr.lastIndexOf(o);
+ }
+
+ @Override
+ public ListIterator listIterator() {
+ return listIterator(0);
+ }
+
+ @Override
+ public ListIterator listIterator(int index) {
+ return new ListIterator() {
+
+ final JsonArray refArr = arr;
+ final ListIterator it = arr.listIterator(index);
+
+ @Override
+ public boolean hasNext() {
+ if (refArr == arr) {
+ return it.hasNext();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public JsonValue next() {
+ if (refArr == arr) {
+ return it.next();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ if (refArr == arr) {
+ return it.hasPrevious();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public JsonValue previous() {
+ if (refArr == arr) {
+ return it.previous();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public int nextIndex() {
+ if (refArr == arr) {
+ return it.nextIndex();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public int previousIndex() {
+ if (refArr == arr) {
+ return it.previousIndex();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ it.remove(); // will throw exception
+ }
+
+ @Override
+ public void set(JsonValue e) {
+ it.set(e); // will throw exception
+ }
+
+ @Override
+ public void add(JsonValue e) {
+ it.add(e); // will throw exception
+ }
+ };
+ }
+
+ @Override
+ public List subList(int fromIndex, int toIndex) {
+ return arr.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public int hashCode() {
+ return arr != null ? arr.hashCode() : 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return this.arr == null;
+ } else if (obj instanceof JsonArrayProxy) {
+ return this.arr.equals(((JsonArrayProxy) obj).arr);
+ }
+ return arr.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return arr != null ? arr.toString() : null;
+ }
+ }
+
+ private static class JsonObjectProxy implements JsonObject {
+
+ private JsonObject obj;
+
+ JsonObjectProxy(JsonObject obj) {
+ this.obj = obj;
+ }
+
+ @Override
+ public JsonArray getJsonArray(String name) {
+ return obj.getJsonArray(name);
+ }
+
+ @Override
+ public JsonObject getJsonObject(String name) {
+ return obj.getJsonObject(name);
+ }
+
+ @Override
+ public JsonNumber getJsonNumber(String name) {
+ return obj.getJsonNumber(name);
+ }
+
+ @Override
+ public JsonString getJsonString(String name) {
+ return obj.getJsonString(name);
+ }
+
+ @Override
+ public String getString(String name) {
+ return obj.getString(name);
+ }
+
+ @Override
+ public String getString(String name, String defaultValue) {
+ return obj.getString(name, defaultValue);
+ }
+
+ @Override
+ public int getInt(String name) {
+ return obj.getInt(name);
+ }
+
+ @Override
+ public int getInt(String name, int defaultValue) {
+ return obj.getInt(name, defaultValue);
+ }
+
+ @Override
+ public boolean getBoolean(String name) {
+ return obj.getBoolean(name);
+ }
+
+ @Override
+ public boolean getBoolean(String name, boolean defaultValue) {
+ return obj.getBoolean(name, defaultValue);
+ }
+
+ @Override
+ public boolean isNull(String name) {
+ return obj.isNull(name);
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return obj.getValueType();
+ }
+
+ @Override
+ public int size() {
+ return obj.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return obj.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return obj.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return obj.containsValue(value);
+ }
+
+ @Override
+ public JsonValue get(Object key) {
+ return obj.get(key);
+ }
+
+ @Override
+ public JsonValue put(String key, JsonValue value) {
+ JsonValue oldValue = obj.get(key);
+ obj = jsonBuilderFactory.createObjectBuilder(obj).add(key, value).build();
+ return oldValue;
+ }
+
+ @Override
+ public JsonValue remove(Object key) {
+ JsonValue oldValue = obj.get(key);
+ if (oldValue != null) {
+ obj = jsonBuilderFactory.createObjectBuilder(obj).remove(key.toString()).build();
+ return oldValue;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends JsonValue> m) {
+ if (m.isEmpty()) {
+ return;
+ }
+ JsonObjectBuilder builder = jsonBuilderFactory.createObjectBuilder(obj);
+ for (Map.Entry extends String, ? extends JsonValue> e : m.entrySet()) {
+ builder.add(e.getKey(), e.getValue());
+ }
+ obj = builder.build();
+ }
+
+ @Override
+ public void clear() {
+ obj = jsonBuilderFactory.createObjectBuilder().build();
+ }
+
+ @Override
+ public Set keySet() {
+ return obj.keySet();
+ }
+
+ @Override
+ public Collection values() {
+ return obj.values();
+ }
+
+ @Override
+ public Set> entrySet() {
+ return new AbstractSet>() {
+
+ final JsonObject refObj = obj;
+
+ @Override
+ public Iterator> iterator() {
+ if (refObj != obj) {
+ throw new ConcurrentModificationException();
+ }
+ return new Iterator>() {
+
+ final Iterator> it = obj.entrySet().iterator();
+
+ @Override
+ public boolean hasNext() {
+ if (refObj == obj) {
+ return it.hasNext();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public Map.Entry next() {
+ if (refObj == obj) {
+ return it.next();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ if (refObj == obj) {
+ return obj.size();
+ } else {
+ throw new ConcurrentModificationException();
+ }
+ }
+ };
+ }
+
+ @Override
+ public int hashCode() {
+ return obj != null ? obj.hashCode() : 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return this.obj == null;
+ } else if (obj instanceof JsonObjectProxy) {
+ return this.obj.equals(((JsonObjectProxy) obj).obj);
+ }
+ return this.obj.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return obj != null ? obj.toString() : null;
+ }
+ }
+}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
index 0c5f169ef..773d82a56 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
@@ -7,8 +7,6 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -16,11 +14,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
public class JsonOrgJsonProvider extends AbstractJsonProvider {
- private static final Logger logger = LoggerFactory.getLogger(GsonJsonProvider.class);
-
@Override
public Object parse(String json) throws InvalidJsonException {
try {
@@ -147,17 +144,14 @@ public boolean isMap(Object obj) {
return (obj instanceof JSONObject);
}
+ @SuppressWarnings("unchecked")
@Override
public Collection getPropertyKeys(Object obj) {
JSONObject jsonObject = toJsonObject(obj);
- List keys = new ArrayList();
try {
- for (int i = 0; i < jsonObject.names().length(); i++) {
- String key = (String) jsonObject.names().get(i);
- keys.add(key);
-
- }
- return keys;
+ if(Objects.isNull(jsonObject.names()))
+ return new ArrayList<>();
+ return jsonObject.keySet();
} catch (JSONException e) {
throw new JsonPathException(e);
}
@@ -174,7 +168,8 @@ public int length(Object obj) {
return ((String) obj).length();
}
}
- throw new JsonPathException("length operation can not applied to " + obj != null ? obj.getClass().getName() : "null");
+ throw new JsonPathException("length operation can not applied to " + (obj != null ? obj.getClass().getName()
+ : "null"));
}
@Override
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java
index fa7dda411..a7fcd711e 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java
@@ -15,10 +15,11 @@
package com.jayway.jsonpath.spi.json;
import com.jayway.jsonpath.InvalidJsonException;
-
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
+
public interface JsonProvider {
static final Object UNDEFINED = new Object();
@@ -31,6 +32,15 @@ public interface JsonProvider {
*/
Object parse(String json) throws InvalidJsonException;
+ /**
+ * Parse the given json bytes in UTF-8 encoding
+ * @param json json bytes to parse
+ * @return Object representation of json
+ * @throws InvalidJsonException
+ */
+ default Object parse(byte[] json) throws InvalidJsonException {
+ return parse(new String(json, StandardCharsets.UTF_8));
+ }
/**
* Parse the given json string
* @param jsonStream input stream to parse
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonSmartJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonSmartJsonProvider.java
index 66172c5eb..5e68ffac1 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonSmartJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonSmartJsonProvider.java
@@ -83,6 +83,8 @@ public String toJson(Object obj) {
return JSONObject.toJSONString((Map) obj, JSONStyle.LT_COMPRESS);
} else if (obj instanceof List) {
return JSONArray.toJSONString((List>) obj, JSONStyle.LT_COMPRESS);
+ } else if (obj instanceof Number || obj instanceof Boolean){
+ return JSONValue.toJSONString(obj);
} else {
throw new UnsupportedOperationException(obj.getClass().getName() + " can not be converted to JSON");
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/mapper/JakartaMappingProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/mapper/JakartaMappingProvider.java
new file mode 100644
index 000000000..377bf61bd
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/mapper/JakartaMappingProvider.java
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2011 the original author or authors.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jayway.jsonpath.spi.mapper;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.Set;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.TypeRef;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonException;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.JsonString;
+import jakarta.json.JsonStructure;
+import jakarta.json.JsonValue;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import jakarta.json.bind.JsonbException;
+import jakarta.json.stream.JsonLocation;
+import jakarta.json.stream.JsonParser;
+
+public class JakartaMappingProvider implements MappingProvider {
+
+ private final Jsonb jsonb;
+ private Method jsonToClassMethod, jsonToTypeMethod;
+
+ public JakartaMappingProvider() {
+ this.jsonb = JsonbBuilder.create();
+ this.jsonToClassMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Class.class);
+ this.jsonToTypeMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Type.class);
+ }
+
+ public JakartaMappingProvider(JsonbConfig jsonbConfiguration) {
+ this.jsonb = JsonbBuilder.create(jsonbConfiguration);
+ this.jsonToClassMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Class.class);
+ this.jsonToTypeMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Type.class);
+ }
+
+ /**
+ * Maps supplied JSON source {@code Object} to a given target class or collection.
+ * This implementation ignores the JsonPath's {@link Configuration} argument.
+ */
+ @Override
+ public T map(Object source, Class targetType, Configuration configuration) {
+ @SuppressWarnings("unchecked")
+ T result = (T) mapImpl(source, targetType);
+ return result;
+ }
+
+ /**
+ * Maps supplied JSON source {@code Object} to a given target type or collection.
+ * This implementation ignores the JsonPath's {@link Configuration} argument.
+ *
+ * Method may produce a {@code ClassCastException} on an attempt to cast
+ * the result of JSON mapping operation to a requested target type, especially if
+ * a parameterized generic type is used.
+ */
+ @Override
+ public T map(Object source, final TypeRef targetType, Configuration configuration) {
+ @SuppressWarnings("unchecked")
+ T result = (T) mapImpl(source, targetType.getType());
+ return result;
+ }
+
+ private Object mapImpl(Object source, final Type targetType) {
+ if (source == null || source == JsonValue.NULL) {
+ return null;
+ }
+ if (source == JsonValue.TRUE) {
+ if (Boolean.class.equals(targetType)) {
+ return Boolean.TRUE;
+ } else {
+ String className = targetType.toString();
+ throw new MappingException("JSON boolean (true) cannot be mapped to " + className);
+ }
+ }
+ if (source == JsonValue.FALSE) {
+ if (Boolean.class.equals(targetType)) {
+ return Boolean.FALSE;
+ } else {
+ String className = targetType.toString();
+ throw new MappingException("JSON boolean (false) cannot be mapped to " + className);
+ }
+ } else if (source instanceof JsonString) {
+ if (String.class.equals(targetType)) {
+ return ((JsonString) source).getChars();
+ } else {
+ String className = targetType.toString();
+ throw new MappingException("JSON string cannot be mapped to " + className);
+ }
+ } else if (source instanceof JsonNumber) {
+ JsonNumber jsonNumber = (JsonNumber) source;
+ if (jsonNumber.isIntegral()) {
+ return mapIntegralJsonNumber(jsonNumber, getRawClass(targetType));
+ } else {
+ return mapDecimalJsonNumber(jsonNumber, getRawClass(targetType));
+ }
+ }
+ if (source instanceof JsonArrayBuilder) {
+ source = ((JsonArrayBuilder) source).build();
+ } else if (source instanceof JsonObjectBuilder) {
+ source = ((JsonObjectBuilder) source).build();
+ }
+ if (source instanceof Collection) {
+ // this covers both List and JsonArray from JSON-P spec
+ Class> rawTargetType = getRawClass(targetType);
+ Type targetTypeArg = getFirstTypeArgument(targetType);
+ Collection result = newCollectionOfType(rawTargetType);
+ for (Object srcValue : (Collection>) source) {
+ if (srcValue instanceof JsonObject) {
+ if (targetTypeArg != null) {
+ result.add(mapImpl(srcValue, targetTypeArg));
+ } else {
+ result.add(srcValue);
+ }
+ } else {
+ result.add(unwrapJsonValue(srcValue));
+ }
+ }
+ return result;
+ } else if (source instanceof JsonObject) {
+ if (targetType instanceof Class) {
+ if (jsonToClassMethod != null) {
+ try {
+ JsonParser jsonParser = new JsonStructureToParserAdapter((JsonStructure) source);
+ return jsonToClassMethod.invoke(jsonb, jsonParser, (Class>) targetType);
+ } catch (Exception e){
+ throw new MappingException(e);
+ }
+ } else {
+ try {
+ // Fallback databinding approach for JSON-B API implementations without
+ // explicit support for use of JsonParser in their public API. The approach
+ // is essentially first to serialize given value into JSON, and then bind
+ // it to data object of given class.
+ String json = source.toString();
+ return jsonb.fromJson(json, (Class>) targetType);
+ } catch (JsonbException e){
+ throw new MappingException(e);
+ }
+ }
+ } else if (targetType instanceof ParameterizedType) {
+ if (jsonToTypeMethod != null) {
+ try {
+ JsonParser jsonParser = new JsonStructureToParserAdapter((JsonStructure) source);
+ return jsonToTypeMethod.invoke(jsonb, jsonParser, (Type) targetType);
+ } catch (Exception e){
+ throw new MappingException(e);
+ }
+ } else {
+ try {
+ // Fallback databinding approach for JSON-B API implementations without
+ // explicit support for use of JsonParser in their public API. The approach
+ // is essentially first to serialize given value into JSON, and then bind
+ // the JSON string to data object of given type.
+ String json = source.toString();
+ return jsonb.fromJson(json, (Type) targetType);
+ } catch (JsonbException e){
+ throw new MappingException(e);
+ }
+ }
+ } else {
+ throw new MappingException("JSON object cannot be databind to " + targetType);
+ }
+ } else {
+ return source;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private T mapIntegralJsonNumber(JsonNumber jsonNumber, Class> targetType) {
+ if (targetType.isPrimitive()) {
+ if (int.class.equals(targetType)) {
+ return (T) Integer.valueOf(jsonNumber.intValueExact());
+ } else if (long.class.equals(targetType)) {
+ return (T) Long.valueOf(jsonNumber.longValueExact());
+ }
+ } else if (Integer.class.equals(targetType)) {
+ return (T) Integer.valueOf(jsonNumber.intValueExact());
+ } else if (Long.class.equals(targetType)) {
+ return (T) Long.valueOf(jsonNumber.longValueExact());
+ } else if (BigInteger.class.equals(targetType)) {
+ return (T) jsonNumber.bigIntegerValueExact();
+ } else if (BigDecimal.class.equals(targetType)) {
+ return (T) jsonNumber.bigDecimalValue();
+ }
+
+ String className = targetType.getSimpleName();
+ throw new MappingException("JSON integral number cannot be mapped to " + className);
+ }
+
+ @SuppressWarnings("unchecked")
+ private T mapDecimalJsonNumber(JsonNumber jsonNumber, Class> targetType) {
+ if (targetType.isPrimitive()) {
+ if (float.class.equals(targetType)) {
+ return (T) new Float(jsonNumber.doubleValue());
+ } else if (double.class.equals(targetType)) {
+ return (T) Double.valueOf(jsonNumber.doubleValue());
+ }
+ } else if (Float.class.equals(targetType)) {
+ return (T) new Float(jsonNumber.doubleValue());
+ } else if (Double.class.equals(targetType)) {
+ return (T) Double.valueOf(jsonNumber.doubleValue());
+ } else if (BigDecimal.class.equals(targetType)) {
+ return (T) jsonNumber.bigDecimalValue();
+ }
+
+ String className = targetType.getSimpleName();
+ throw new MappingException("JSON decimal number cannot be mapped to " + className);
+ }
+
+ private Object unwrapJsonValue(Object jsonValue) {
+ if (jsonValue == null) {
+ return null;
+ }
+ if (!(jsonValue instanceof JsonValue)) {
+ return jsonValue;
+ }
+ switch (((JsonValue) jsonValue).getValueType()) {
+ case ARRAY:
+ // TODO do we unwrap JsonObjectArray proxies?
+ //return ((JsonArray) jsonValue).getValuesAs(JsonValue.class);
+ return ((JsonArray) jsonValue).getValuesAs((JsonValue v) -> unwrapJsonValue(v));
+ case OBJECT:
+ throw new IllegalArgumentException("Use map() method to databind a JsonObject");
+ case STRING:
+ return ((JsonString) jsonValue).getString();
+ case NUMBER:
+ if (((JsonNumber) jsonValue).isIntegral()) {
+ //return ((JsonNumber) jsonValue).bigIntegerValueExact();
+ try {
+ return ((JsonNumber) jsonValue).intValueExact();
+ } catch (ArithmeticException e) {
+ return ((JsonNumber) jsonValue).longValueExact();
+ }
+ } else {
+ //return ((JsonNumber) jsonValue).bigDecimalValue();
+ return ((JsonNumber) jsonValue).doubleValue();
+ }
+ case TRUE:
+ return Boolean.TRUE;
+ case FALSE:
+ return Boolean.FALSE;
+ case NULL:
+ return null;
+ default:
+ return jsonValue;
+ }
+ }
+
+ /**
+ * Creates new instance of {@code Collection} type specified by the
+ * argument. If the argument refers to an interface, then a matching
+ * Java standard implementation is returned; if it is a concrete class,
+ * then method attempts to instantiate an object of that class given
+ * there is a public no-arg constructor available.
+ *
+ * @param collectionType collection type; may be an interface or a class
+ * @return instance of collection type identified by the argument
+ * @throws MappingException on a type that cannot be safely instantiated
+ */
+ private Collection newCollectionOfType(Class> collectionType) throws MappingException {
+ if (Collection.class.isAssignableFrom(collectionType)) {
+ if (!collectionType.isInterface()) {
+ @SuppressWarnings("unchecked")
+ Collection coll = (Collection) newNoArgInstance(collectionType);
+ return coll;
+ } else if (List.class.isAssignableFrom(collectionType)) {
+ return new java.util.LinkedList();
+ } else if (Set.class.isAssignableFrom(collectionType)) {
+ return new java.util.LinkedHashSet();
+ } else if (Queue.class.isAssignableFrom(collectionType)) {
+ return new java.util.LinkedList();
+ }
+ }
+ String className = collectionType.getSimpleName();
+ throw new MappingException("JSON array cannot be mapped to " + className);
+ }
+
+ /**
+ * Lists all publicly accessible constructors for the {@code Class}
+ * identified by the argument, including any constructors inherited
+ * from superclasses, and uses a no-args constructor, if available,
+ * to create a new instance of the class. If argument is interface,
+ * this method returns {@code null}.
+ *
+ * @param targetType class type to create instance of
+ * @return an instance of the class represented by the argument
+ * @throws MappingException if no-arg public constructor is not there
+ */
+ private Object newNoArgInstance(Class> targetType) throws MappingException {
+ if (targetType.isInterface()) {
+ return null;
+ } else {
+ for (Constructor> ctr : targetType.getConstructors()) {
+ if (ctr.getParameterCount() == 0) {
+ try {
+ return ctr.newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new MappingException(e);
+ } catch (IllegalArgumentException e) {
+ // never happens
+ }
+ }
+ }
+ String className = targetType.getSimpleName();
+ throw new MappingException("Unable to find no-arg ctr for " + className);
+ }
+ }
+
+ /**
+ * Locates optional API method on the supplied JSON-B API implementation class with
+ * the supplied name and parameter types. Searches the superclasses up to
+ * {@code Object}, but ignores interfaces and default interface methods. Returns
+ * {@code null} if no {@code Method} can be found.
+ *
+ * @param clazz the implementation class to reflect upon
+ * @param name the name of the method
+ * @param paramTypes the parameter types of the method
+ * @return the {@code Method} reference, or {@code null} if none found
+ */
+ private Method findMethod(Class> clazz, String name, Class>... paramTypes) {
+ while (clazz != null && !clazz.isInterface()) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ final int mods = method.getModifiers();
+ if (Modifier.isPublic(mods) && !Modifier.isAbstract(mods) &&
+ name.equals(method.getName()) &&
+ Arrays.equals(paramTypes, method.getParameterTypes())) {
+ return method;
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return null;
+ }
+
+ private Class> getRawClass(Type targetType) {
+ if (targetType instanceof Class) {
+ return (Class>) targetType;
+ } else if (targetType instanceof ParameterizedType) {
+ return (Class>) ((ParameterizedType) targetType).getRawType();
+ } else if (targetType instanceof GenericArrayType) {
+ String typeName = targetType.getTypeName();
+ throw new MappingException("Cannot map JSON element to " + typeName);
+ } else {
+ String typeName = targetType.getTypeName();
+ throw new IllegalArgumentException("TypeRef not supported: " + typeName);
+ }
+ }
+
+ private Type getFirstTypeArgument(Type targetType) {
+ if (targetType instanceof ParameterizedType) {
+ Type[] args = ((ParameterizedType) targetType).getActualTypeArguments();
+ if (args != null && args.length > 0) {
+ if (args[0] instanceof Class) {
+ return (Class>) args[0];
+ } else if (args[0] instanceof ParameterizedType) {
+ return (ParameterizedType) args[0];
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Runtime adapter for {@link JsonParser} to pull the JSON objects and values from
+ * a {@link JsonStructure} content tree instead of plain JSON string.
+ *
+ * JSON-B API 1.0 final specification does not include any public methods to read JSON
+ * content from pre-parsed {@link JsonStructure} tree, so this parser is used by the
+ * Jakarta EE mapping provider above to feed in JSON content to JSON-B implementation.
+ */
+ private static class JsonStructureToParserAdapter implements JsonParser {
+
+ private JsonStructureScope scope;
+ private Event state;
+ private final Deque ancestry = new ArrayDeque<>();
+
+ JsonStructureToParserAdapter(JsonStructure jsonStruct) {
+ scope = createScope(jsonStruct);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !((state == Event.END_ARRAY || state == Event.END_OBJECT) && ancestry.isEmpty());
+ }
+
+ @Override
+ public Event next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ if (state == null) {
+ state = scope instanceof JsonArrayScope ? Event.START_ARRAY : Event.START_OBJECT;
+ } else {
+ if (state == Event.END_ARRAY || state == Event.END_OBJECT) {
+ scope = ancestry.pop();
+ }
+ if (scope instanceof JsonArrayScope) { // array scope
+ if (scope.hasNext()) {
+ scope.next();
+ state = getState(scope.getValue());
+ if (state == Event.START_ARRAY || state == Event.START_OBJECT) {
+ ancestry.push(scope);
+ scope = createScope(scope.getValue());
+ }
+ } else {
+ state = Event.END_ARRAY;
+ }
+ } else { // object scope
+ if (state == Event.KEY_NAME) {
+ state = getState(scope.getValue());
+ if (state == Event.START_ARRAY || state == Event.START_OBJECT) {
+ ancestry.push(scope);
+ scope = createScope(scope.getValue());
+ }
+ } else {
+ if (scope.hasNext()) {
+ scope.next();
+ state = Event.KEY_NAME;
+ } else {
+ state = Event.END_OBJECT;
+ }
+ }
+ }
+ }
+ return state;
+ }
+
+ @Override
+ public String getString() {
+ switch (state) {
+ case KEY_NAME:
+ return ((JsonObjectScope) scope).getKey();
+ case VALUE_STRING:
+ return ((JsonString) scope.getValue()).getString();
+ case VALUE_NUMBER:
+ return ((JsonNumber) scope.getValue()).toString();
+ default:
+ throw new IllegalStateException("Parser is not in KEY_NAME, VALUE_STRING, or VALUE_NUMBER state");
+ }
+ }
+
+ @Override
+ public boolean isIntegralNumber() {
+ if (state == Event.VALUE_NUMBER) {
+ return ((JsonNumber) scope.getValue()).isIntegral();
+ }
+ throw new IllegalStateException("Target json value must a number, not " + state);
+ }
+
+ @Override
+ public int getInt() {
+ if (state == Event.VALUE_NUMBER) {
+ return ((JsonNumber) scope.getValue()).intValue();
+ }
+ throw new IllegalStateException("Target json value must a number, not " + state);
+ }
+
+ @Override
+ public long getLong() {
+ if (state == Event.VALUE_NUMBER) {
+ return ((JsonNumber) scope.getValue()).longValue();
+ }
+ throw new IllegalStateException("Target json value must a number, not " + state);
+ }
+
+ @Override
+ public BigDecimal getBigDecimal() {
+ if (state == Event.VALUE_NUMBER) {
+ return ((JsonNumber) scope.getValue()).bigDecimalValue();
+ }
+ throw new IllegalStateException("Target json value must a number, not " + state);
+ }
+
+ @Override
+ public JsonLocation getLocation() {
+ throw new UnsupportedOperationException("JSON-P adapter does not support getLocation()");
+ }
+
+ @Override
+ public void skipArray() {
+ if (scope instanceof JsonArrayScope) {
+ while (scope.hasNext()) {
+ scope.next();
+ }
+ state = Event.END_ARRAY;
+ }
+ }
+
+ @Override
+ public void skipObject() {
+ if (scope instanceof JsonObjectScope) {
+ while (scope.hasNext()) {
+ scope.next();
+ }
+ state = Event.END_OBJECT;
+ }
+ }
+
+ @Override
+ public void close() {
+ // JSON objects are read-only
+ }
+
+ private JsonStructureScope createScope(JsonValue value) {
+ if (value instanceof JsonArray) {
+ return new JsonArrayScope((JsonArray) value);
+ } else if (value instanceof JsonObject) {
+ return new JsonObjectScope((JsonObject) value);
+ }
+ throw new JsonException("Cannot create JSON iterator for " + value);
+ }
+
+ private Event getState(JsonValue value) {
+ switch (value.getValueType()) {
+ case ARRAY:
+ return Event.START_ARRAY;
+ case OBJECT:
+ return Event.START_OBJECT;
+ case STRING:
+ return Event.VALUE_STRING;
+ case NUMBER:
+ return Event.VALUE_NUMBER;
+ case TRUE:
+ return Event.VALUE_TRUE;
+ case FALSE:
+ return Event.VALUE_FALSE;
+ case NULL:
+ return Event.VALUE_NULL;
+ default:
+ throw new JsonException("Unknown value type " + value.getValueType());
+ }
+ }
+ }
+
+ private static abstract class JsonStructureScope implements Iterator {
+ /**
+ * Returns current {@link JsonValue}, that the parser is pointing on. Before
+ * the {@link #next()} method has been called, this returns {@code null}.
+ *
+ * @return JsonValue value object.
+ */
+ abstract JsonValue getValue();
+ }
+
+ private static class JsonArrayScope extends JsonStructureScope {
+ private final Iterator it;
+ private JsonValue value;
+
+ JsonArrayScope(JsonArray array) {
+ this.it = array.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public JsonValue next() {
+ value = it.next();
+ return value;
+ }
+
+ @Override
+ JsonValue getValue() {
+ return value;
+ }
+ }
+
+ private static class JsonObjectScope extends JsonStructureScope {
+ private final Iterator> it;
+ private JsonValue value;
+ private String key;
+
+ JsonObjectScope(JsonObject object) {
+ this.it = object.entrySet().iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public JsonValue next() {
+ Map.Entry next = it.next();
+ this.key = next.getKey();
+ this.value = next.getValue();
+ return value;
+ }
+
+ @Override
+ JsonValue getValue() {
+ return value;
+ }
+
+ String getKey() {
+ return key;
+ }
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
index 714aa92b4..eac44cdb1 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
@@ -5,12 +5,14 @@
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.json.JakartaJsonProvider;
import com.jayway.jsonpath.spi.json.JettisonProvider;
import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider;
import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.json.TapestryJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import com.jayway.jsonpath.spi.mapper.JakartaMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonOrgMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
import com.jayway.jsonpath.spi.mapper.TapestryMappingProvider;
@@ -55,10 +57,23 @@ public class BaseTest {
.build();
public static final Configuration TAPESTRY_JSON_CONFIGURATION = Configuration
- .builder()
- .mappingProvider(new TapestryMappingProvider())
- .jsonProvider(TapestryJsonProvider.INSTANCE)
- .build();
+ .builder()
+ .mappingProvider(new TapestryMappingProvider())
+ .jsonProvider(TapestryJsonProvider.INSTANCE)
+ .build();
+
+ public static final Configuration JAKARTA_JSON_CONFIGURATION = Configuration
+ .builder()
+ .mappingProvider(new JakartaMappingProvider())
+ .jsonProvider(new JakartaJsonProvider())
+ .build();
+
+ // extension to Jakarta EE 9 JSON-P with mutable objects and array
+ public static final Configuration JAKARTA_JSON_RW_CONFIGURATION = Configuration
+ .builder()
+ .mappingProvider(new JakartaMappingProvider())
+ .jsonProvider(new JakartaJsonProvider(true))
+ .build();
public static final String JSON_BOOK_DOCUMENT =
"{ " +
@@ -118,6 +133,26 @@ public class BaseTest {
" \"@id\" : \"ID\"\n" +
"}";
+ public static String JSON_BOOK_STORE_DOCUMENT = "{\n" +
+ " \"store\": {\n" +
+ " \"book\": [\n" +
+ " {\n" +
+ " \"category\": \"reference\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\": \"fiction\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\": \"fiction\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\": \"fiction\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"expensive\": 10\n" +
+ "}";
+
public Predicate.PredicateContext createPredicateContext(final Object check) {
return new PredicateContextImpl(check, check, Configuration.defaultConfiguration(), new HashMap());
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Configurations.java b/json-path/src/test/java/com/jayway/jsonpath/Configurations.java
index 1da98142d..537d2be0e 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/Configurations.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/Configurations.java
@@ -3,10 +3,12 @@
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.json.JakartaJsonProvider;
import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider;
import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import com.jayway.jsonpath.spi.mapper.JakartaMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonOrgMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
@@ -44,6 +46,12 @@ public class Configurations {
.jsonProvider(new JsonSmartJsonProvider())
.build();
+ public static final Configuration JAKARTA_CONFIGURATION = Configuration
+ .builder()
+ .mappingProvider(new JakartaMappingProvider())
+ .jsonProvider(new JakartaJsonProvider())
+ .build();
+
public static Iterable configurations() {
return Arrays.asList(
JSON_SMART_CONFIGURATION
@@ -51,6 +59,7 @@ public static Iterable configurations() {
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
,JSON_ORG_CONFIGURATION
+ ,JAKARTA_CONFIGURATION
);
}
@@ -59,6 +68,7 @@ public static Iterable objectMappingConfigurations() {
GSON_CONFIGURATION
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
+ ,JAKARTA_CONFIGURATION
);
}
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java
index 948ca75fb..37e37f79d 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/FilterParseTest.java
@@ -1,9 +1,9 @@
package com.jayway.jsonpath;
-import java.util.Collections;
import org.assertj.core.api.Assertions;
import org.junit.Test;
+import java.util.Collections;
import java.util.regex.Pattern;
import static com.jayway.jsonpath.Criteria.where;
@@ -152,6 +152,24 @@ public void a_subsetof_filter_can_be_serialized() {
assertThat(filter).isEqualTo(parsed);
}
+ @Test
+ public void a_anyof_filter_can_be_serialized() {
+
+ String filter = filter(where("a").anyof(Collections.emptyList())).toString();
+ String parsed = parse("[?(@['a'] ANYOF [])]").toString();
+
+ assertThat(filter).isEqualTo(parsed);
+ }
+
+ @Test
+ public void a_noneof_filter_can_be_serialized() {
+
+ String filter = filter(where("a").noneof(Collections.emptyList())).toString();
+ String parsed = parse("[?(@['a'] NONEOF [])]").toString();
+
+ assertThat(filter).isEqualTo(parsed);
+ }
+
@Test
public void a_exists_filter_can_be_serialized() {
diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
index e786ae83a..c7948994c 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
@@ -1,6 +1,5 @@
package com.jayway.jsonpath;
-import java.util.ArrayList;
import org.assertj.core.util.Lists;
import org.junit.Test;
@@ -29,7 +28,8 @@ public class FilterTest extends BaseTest {
" \"char-key\" : \"c\", " +
" \"arr-empty\" : [], " +
" \"int-arr\" : [0,1,2,3,4], " +
- " \"string-arr\" : [\"a\",\"b\",\"c\",\"d\",\"e\"] " +
+ " \"string-arr\" : [\"a\",\"b\",\"c\",\"d\",\"e\"], " +
+ " \"obj\": {\"foo\": \"bar\"}" +
"}"
);
@@ -264,6 +264,17 @@ public void string_regex_evals() {
assertThat(filter(where("int-key").regex(Pattern.compile("^string$"))).apply(createPredicateContext(json))).isEqualTo(false);
}
+ @Test
+ public void list_regex_evals() {
+ assertThat(filter(where("string-arr").regex(Pattern.compile("^d$"))).apply(createPredicateContext(json))).isEqualTo(true);
+ assertThat(filter(where("string-arr").regex(Pattern.compile("^q$"))).apply(createPredicateContext(json))).isEqualTo(false);
+ }
+
+ @Test
+ public void obj_regex_doesnt_break() {
+ assertThat(filter(where("obj").regex(Pattern.compile("^foo$"))).apply(createPredicateContext(json))).isEqualTo(false);
+ }
+
//----------------------------------------------------------------------------
//
// JSON equality
@@ -279,7 +290,8 @@ public void json_evals() {
Filter farr = parse("[?(@.foo == " + arr + ")]");
//Filter fobjF = parse("[?(@.foo == " + nest + ")]");
//Filter fobjT = parse("[?(@.bar == " + nest + ")]");
- assertThat(farr.apply(context)).isEqualTo(true);
+ boolean apply = farr.apply(context);
+ assertThat(apply).isEqualTo(true);
//assertThat(fobjF.apply(context)).isEqualTo(false);
//assertThat(fobjT.apply(context)).isEqualTo(true);
}
@@ -378,6 +390,36 @@ public void array_subsetof_evals() {
assertThat(filter(where("string-arr").subsetof(list)).apply(createPredicateContext(json))).isEqualTo(false);
}
+ //----------------------------------------------------------------------------
+ //
+ // ANYOF
+ //
+ //----------------------------------------------------------------------------
+ @Test
+ public void array_anyof_evals() {
+ List list = Lists.newArrayList("a", "z");
+ assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true);
+ list = Lists.newArrayList("z", "b", "a");
+ assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(true);
+ list = Lists.newArrayList("x", "y", "z");
+ assertThat(filter(where("string-arr").anyof(list)).apply(createPredicateContext(json))).isEqualTo(false);
+ }
+
+ //----------------------------------------------------------------------------
+ //
+ // NONEOF
+ //
+ //----------------------------------------------------------------------------
+ @Test
+ public void array_noneof_evals() {
+ List list = Lists.newArrayList("a", "z");
+ assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false);
+ list = Lists.newArrayList("z", "b", "a");
+ assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(false);
+ list = Lists.newArrayList("x", "y", "z");
+ assertThat(filter(where("string-arr").noneof(list)).apply(createPredicateContext(json))).isEqualTo(true);
+ }
+
//----------------------------------------------------------------------------
//
// EXISTS
@@ -417,13 +459,13 @@ public void type_evals() {
//----------------------------------------------------------------------------
@Test
public void not_empty_evals() {
- assertThat(filter(where("string-key").notEmpty()).apply(createPredicateContext(json))).isEqualTo(true);
- assertThat(filter(where("string-key-empty").notEmpty()).apply(createPredicateContext(json))).isEqualTo(false);
+ assertThat(filter(where("string-key").empty(false)).apply(createPredicateContext(json))).isEqualTo(true);
+ assertThat(filter(where("string-key-empty").empty(false)).apply(createPredicateContext(json))).isEqualTo(false);
- assertThat(filter(where("int-arr").notEmpty()).apply(createPredicateContext(json))).isEqualTo(true);
- assertThat(filter(where("arr-empty").notEmpty()).apply(createPredicateContext(json))).isEqualTo(false);
+ assertThat(filter(where("int-arr").empty(false)).apply(createPredicateContext(json))).isEqualTo(true);
+ assertThat(filter(where("arr-empty").empty(false)).apply(createPredicateContext(json))).isEqualTo(false);
- assertThat(filter(where("null-key").notEmpty()).apply(createPredicateContext(json))).isEqualTo(false);
+ assertThat(filter(where("null-key").empty(false)).apply(createPredicateContext(json))).isEqualTo(false);
}
//----------------------------------------------------------------------------
@@ -515,11 +557,11 @@ public void testFilterWithOrShortCircuit2() throws Exception {
@Test
public void criteria_can_be_parsed() {
- Criteria criteria = Criteria.parse("@.foo == 'baar'");
- assertThat(criteria.toString()).isEqualTo("@['foo'] == 'baar'");
+ Filter criteria = Filter.parse("[?(@.foo == 'baar')]");
+ assertThat(criteria.toString()).isEqualTo("[?(@['foo'] == 'baar')]");
- criteria = Criteria.parse("@.foo");
- assertThat(criteria.toString()).isEqualTo("@['foo']");
+ criteria = Filter.parse("[?(@.foo)]");
+ assertThat(criteria.toString()).isEqualTo("[?(@['foo'])]");
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java
index 5cca22b44..99e4af0af 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/GsonJsonProviderTest.java
@@ -11,6 +11,7 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.ArrayList;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.using;
@@ -145,9 +146,11 @@ public void an_Integer_can_be_converted_to_a_Double() {
@Test
public void list_of_numbers() {
JsonArray objs = using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price");
-
- assertThat(objs.iterator()).extracting("asDouble").containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
-
+ List actual = new ArrayList<>();
+ for (JsonElement obj : objs) {
+ actual.add(obj.getAsDouble());
+ }
+ assertThat(actual).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
}
@Test
diff --git a/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java
index 1a1303443..c428e3e42 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java
@@ -4,6 +4,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +19,47 @@ public class InlineFilterTest extends BaseTest {
private static int bookCount = 4;
+ public static final String MULTI_STORE_JSON_DOCUMENT = "{\n" +
+ " \"store\" : [{\n" +
+ " \"name\": \"First\"," +
+ " \"book\" : [\n" +
+ " {\n" +
+ " \"category\" : \"reference\",\n" +
+ " \"author\" : \"Nigel Rees\",\n" +
+ " \"title\" : \"Sayings of the Century\",\n" +
+ " \"display-price\" : 8.95\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\" : \"fiction\",\n" +
+ " \"author\" : \"Evelyn Waugh\",\n" +
+ " \"title\" : \"Sword of Honour\",\n" +
+ " \"display-price\" : 12.99\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\" : \"fiction\",\n" +
+ " \"author\" : \"Herman Melville\",\n" +
+ " \"title\" : \"Moby Dick\",\n" +
+ " \"isbn\" : \"0-553-21311-3\",\n" +
+ " \"display-price\" : 8.99\n" +
+ " },\n" +
+ " {\n" +
+ " \"category\" : \"fiction\",\n" +
+ " \"author\" : \"J. R. R. Tolkien\",\n" +
+ " \"title\" : \"The Lord of the Rings\",\n" +
+ " \"isbn\" : \"0-395-19395-8\",\n" +
+ " \"display-price\" : 22.99\n" +
+ " }]\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"Second\",\n" +
+ " \"book\": [\n" +
+ " {\n" +
+ " \"category\" : \"fiction\",\n" +
+ " \"author\" : \"Ernest Hemmingway\",\n" +
+ " \"title\" : \"The Old Man and the Sea\",\n" +
+ " \"display-price\" : 12.99\n" +
+ " }]\n" +
+ " }]}";
private Configuration conf = Configurations.GSON_CONFIGURATION;
@@ -32,9 +74,14 @@ public static Iterable configurations() {
@Test
public void root_context_can_be_referred_in_predicate() {
- List prices = using(conf).parse(JSON_DOCUMENT).read("store.book[?(@.display-price <= $.max-price)].display-price", List.class);
+ List> prices = using(conf).parse(JSON_DOCUMENT).read("store.book[?(@.display-price <= $.max-price)].display-price", List.class);
- assertThat(prices).containsAll(asList(8.95D, 8.99D));
+ assertThat(prices.stream().map(this::asDouble)).containsAll(asList(8.95D, 8.99D));
+ }
+
+ private Double asDouble(Object object) {
+ // For json-org implementation returns a list of big decimals
+ return object instanceof BigDecimal ? ((BigDecimal) object).doubleValue() : (Double) object;
}
@Test
@@ -125,6 +172,12 @@ public void patterns_can_be_evaluated_with_ignore_case() {
assertThat(resLeft).containsExactly("Nigel Rees");
}
+ @Test
+ public void patterns_match_against_lists() {
+ List haveRefBooks = JsonPath.parse(MULTI_STORE_JSON_DOCUMENT).read("$.store[?(@.book[*].category =~ /Reference/i)].name");
+ assertThat(haveRefBooks).containsExactly("First");
+ }
+
@Test
public void negate_exists_check() {
List hasIsbn = JsonPath.parse(JSON_DOCUMENT).read("$.store.book[?(@.isbn)].author");
@@ -193,6 +246,10 @@ public void escaped_literals() {
if(conf.jsonProvider().getClass().getSimpleName().startsWith("Jackson")){
return;
}
+ if(conf.jsonProvider().getClass().getSimpleName().startsWith("Jakarta")){
+ // single quotes are not valid in JSON; see json.org
+ return;
+ }
assertHasOneResult("[\"\\'foo\"]", "$[?(@ == '\\'foo')]", conf);
}
@@ -210,6 +267,16 @@ public void escape_pattern() {
assertHasOneResult("[\"x\"]", "$[?(@ =~ /\\/|x/)]", conf);
}
+ @Test
+ public void escape_pattern_after_literal() {
+ assertHasOneResult("[\"x\"]", "$[?(@ == \"abc\" || @ =~ /\\/|x/)]", conf);
+ }
+
+ @Test
+ public void escape_pattern_before_literal() {
+ assertHasOneResult("[\"x\"]", "$[?(@ =~ /\\/|x/ || @ == \"abc\")]", conf);
+ }
+
@Test
public void filter_evaluation_does_not_break_path_evaluation() {
assertHasOneResult("[{\"s\": \"fo\", \"expected_size\": \"m\"}, {\"s\": \"lo\", \"expected_size\": 2}]", "$[?(@.s size @.expected_size)]", conf);
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_487.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_487.java
new file mode 100644
index 000000000..aa3a034f4
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_487.java
@@ -0,0 +1,26 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+public class Issue_487 {
+
+ public static final Configuration jsonConf = Configuration.defaultConfiguration();
+
+ @Test//(expected = InvalidPathException.class)
+ public void test_read_with_comma_1(){ // originally throws InvalidPathException
+ DocumentContext dc = JsonPath.using(jsonConf)
+ .parse("{ \"key,\" : \"value\" }");
+ Object ans = dc.read(JsonPath.compile("$['key,']"));
+ //System.out.println(ans);
+ assert(ans.toString().equals("value"));
+ }
+
+ @Test
+ public void test_read_with_comma_2(){ // originally passed
+ DocumentContext dc = JsonPath.using(jsonConf)
+ .parse("{ \"key,\" : \"value\" }");
+ Object ans = dc.read(JsonPath.compile("$['key\\,']"));
+ //System.out.println(ans);
+ assert(ans.toString().equals("value"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_537.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_537.java
new file mode 100644
index 000000000..481e51e6a
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_537.java
@@ -0,0 +1,29 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class Issue_537 {
+
+ public static final Configuration jsonConf = Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);
+
+ @Test
+ public void test_read(){ // originally passed
+ Object ans = JsonPath.using(jsonConf).parse("{}").read("missing");
+ assert(ans == null);
+ }
+
+ @Test
+ public void test_renameKey(){ // originally throws PathNotFoundException
+ List ans = JsonPath.using(jsonConf)
+ .parse("{\"list\":[" +
+ "{\"data\":{\"old\":1}}," +
+ "{\"data\":{}}," +
+ "{\"data\":{\"old\":2}}" +
+ "]}")
+ .renameKey("$..data", "old", "new")
+ .read("$.list");
+ assert(ans.toString().equals("[{\"data\":{\"new\":1}},{\"data\":{}},{\"data\":{\"new\":2}}]"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_721.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_721.java
new file mode 100644
index 000000000..98a7ee617
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_721.java
@@ -0,0 +1,36 @@
+package com.jayway.jsonpath;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.DocumentContext;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.Option;
+import org.junit.Test;
+
+public class Issue_721 {
+
+ public static final Configuration jsonConf = Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);
+
+ @Test
+ public void test_delete_1(){ // originally throws PathNotFoundException
+ DocumentContext dc = JsonPath.using(jsonConf)
+ .parse("{\"top\": {\"middle\": null}}")
+ .delete(JsonPath.compile("$.top.middle.bottom"));
+ Object ans = dc.read("$");
+ //System.out.println(ans);
+ assert(ans.toString().equals("{top={middle=null}}"));
+ }
+
+ @Test
+ public void test_delete_2(){ // originally passed
+ DocumentContext dc = JsonPath.using(jsonConf)
+ .parse("[" +
+ "{\"top\": {\"middle\": null}}," +
+ "{\"top\": {\"middle\": {} }}," +
+ "{\"top\": {\"middle\": {bottom: 2} }}," +
+ "]")
+ .delete(JsonPath.compile("$[*].top.middle.bottom"));
+ Object ans = dc.read("$");
+ //System.out.println(ans);
+ assert(ans.toString().equals("[{\"top\":{\"middle\":null}},{\"top\":{\"middle\":{}}},{\"top\":{\"middle\":{}}}]"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_762.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_762.java
new file mode 100644
index 000000000..19183e3cb
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_762.java
@@ -0,0 +1,20 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import static com.jayway.jsonpath.BaseTest.JSON_DOCUMENT;
+
+
+/**
+ * test for issue 762
+ */
+
+public class Issue_762 {
+ @Test
+ public void testParseJsonValue(){
+ assert(JsonPath.parse(5).jsonString().equals("5"));
+ assert(JsonPath.parse(5.0).jsonString().equals("5.0"));
+ assert(JsonPath.parse(true).jsonString().equals("true"));
+ assert(JsonPath.parse(false).jsonString().equals("false"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_786.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_786.java
new file mode 100644
index 000000000..97329451b
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_786.java
@@ -0,0 +1,23 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * test for issue 786
+ */
+public class Issue_786 extends BaseTest{
+
+ @Test
+ public void test(){
+ assertThat(bookLength()).describedAs("First run").isEqualTo(4);
+ assertThat(bookLength()).describedAs("Second run").isEqualTo(4);
+ assertThat(bookLength()).describedAs("Third run").isEqualTo(4);
+ }
+
+ private int bookLength() {
+ return JsonPath.read(JSON_DOCUMENT, "$..book.length()");
+ }
+
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_970.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_970.java
new file mode 100644
index 000000000..25f52b787
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_970.java
@@ -0,0 +1,12 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+public class Issue_970 {
+ @Test
+ public void shouldNotCauseStackOverflow() {
+ assertThatNoException().isThrownBy(() -> Criteria.where("[']',"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/Issue_973.java b/json-path/src/test/java/com/jayway/jsonpath/Issue_973.java
new file mode 100644
index 000000000..a1d05ac34
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/Issue_973.java
@@ -0,0 +1,12 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.*;
+
+public class Issue_973 {
+ @Test
+ public void shouldNotCauseStackOverflow() {
+ assertThatNoException().isThrownBy(() -> Criteria.parse("@[\"\",/\\"));
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderMapperSupportTest.java b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderMapperSupportTest.java
new file mode 100644
index 000000000..abff5c43e
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderMapperSupportTest.java
@@ -0,0 +1,144 @@
+package com.jayway.jsonpath;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
+import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JacksonJsonNodeJsonProviderMapperSupportTest {
+
+ private final TestData testData;
+
+ public JacksonJsonNodeJsonProviderMapperSupportTest(final TestData testData) {
+ this.testData = testData;
+ }
+ @Test
+ public void mapMethod_withJacksonJsonNodeJsonProvider_shouldUsingJsonNodeForMappingValues() {
+ DocumentContext testJsonDocumentContext = cloneDocumentContext(testData.jsonDocumentContext);
+
+ testJsonDocumentContext.map(testData.jsonPath, (value, config) -> {
+ assertThat(value.getClass()).isEqualTo(testData.expectedJsonValueNodeType);
+ return testData.newJsonValue;
+ });
+ assertThat((JsonNode) testJsonDocumentContext.json())
+ .isEqualTo(testData.expectedUpdatedJsonDocument);
+ }
+
+ @Test
+ public void readMethod_withJacksonJsonNodeJsonProvider_shouldReturnJsonNode() {
+ DocumentContext testJsonDocumentContext = cloneDocumentContext(testData.jsonDocumentContext);
+
+ final JsonNode actualJsonValue = testJsonDocumentContext.read(testData.jsonPath);
+ assertThat(actualJsonValue).isEqualTo(testData.expectedJsonValue);
+ }
+
+ @Test
+ public void setMethod_withJacksonJsonNodeJsonProvider_shouldAcceptJsonNode() {
+ DocumentContext testJsonDocumentContext = cloneDocumentContext(testData.jsonDocumentContext);
+
+ testJsonDocumentContext.set(testData.jsonPath, testData.newJsonValue);
+ assertThat((JsonNode) testJsonDocumentContext.json())
+ .isEqualTo(testData.expectedUpdatedJsonDocument);
+ }
+
+ private static class TestData {
+
+ public final DocumentContext jsonDocumentContext;
+ public final String jsonPath;
+ public final JsonNode newJsonValue;
+ public final JsonNode expectedJsonValue;
+ public final Class extends JsonNode> expectedJsonValueNodeType;
+ public final JsonNode expectedUpdatedJsonDocument;
+
+ public TestData(
+ DocumentContext jsonDocumentContext,
+ String jsonPath,
+ JsonNode newJsonValue,
+ JsonNode expectedJsonValue,
+ Class extends JsonNode> expectedJsonValueNodeType,
+ JsonNode expectedUpdatedJsonDocument) {
+ this.jsonDocumentContext = jsonDocumentContext;
+ this.jsonPath = jsonPath;
+ this.newJsonValue = newJsonValue;
+ this.expectedJsonValue = expectedJsonValue;
+ this.expectedJsonValueNodeType = expectedJsonValueNodeType;
+ this.expectedUpdatedJsonDocument = expectedUpdatedJsonDocument;
+ }
+ }
+
+ @Parameterized.Parameters
+ public static List testDataSource() throws Exception {
+ final Configuration configuration = Configuration.builder()
+ .jsonProvider(new JacksonJsonNodeJsonProvider())
+ .mappingProvider(new JacksonMappingProvider())
+ .build();
+ final ParseContext parseContext = JsonPath.using(configuration);
+ final ObjectMapper objectMapper = new ObjectMapper();
+
+ return Arrays.asList(
+ // Single value JSON path
+ new TestData(
+ parseContext.parse("{"
+ + " \"attr1\": \"val1\","
+ + " \"attr2\": \"val2\""
+ + "}"),
+ "$.attr1",
+ objectMapper.readTree("{\"attr1\": \"val1\"}"),
+ objectMapper.readTree("\"val1\""),
+ TextNode.class,
+ objectMapper.readTree("{"
+ + " \"attr1\": {\"attr1\": \"val1\"},"
+ + " \"attr2\": \"val2\""
+ + "}")),
+ // Multi-value JSON path
+ new TestData(
+ parseContext.parse("{"
+ + " \"attr1\": [\"val1\", \"val2\"],"
+ + " \"attr2\": \"val2\""
+ + "}"),
+ "$.attr1[*]",
+ objectMapper.readTree("{\"attr1\": \"val1\"}"),
+ objectMapper.readTree("[\"val1\", \"val2\"]"),
+ TextNode.class,
+ objectMapper.readTree("{"
+ + " \"attr1\": [{\"attr1\": \"val1\"}, {\"attr1\": \"val1\"}],"
+ + " \"attr2\": \"val2\""
+ + "}")),
+ // Multi-value object JSON path
+ new TestData(
+ parseContext.parse("{"
+ + " \"attr1\": ["
+ + " {\"inAttr1\": \"val1a\", \"inAttr2\": \"val2a\"},"
+ + " {\"inAttr1\": \"val1a\", \"inAttr2\": \"val2b\"},"
+ + " {\"inAttr1\": \"val1b\", \"inAttr2\": \"val2c\"}"
+ + " ],"
+ + " \"attr2\": \"val2\""
+ + "}"),
+ "$.attr1.[?(@.inAttr1 == \"val1a\")].inAttr2",
+ objectMapper.readTree("{\"attr1\": \"val1\"}"),
+ objectMapper.readTree("[\"val2a\", \"val2b\"]"),
+ TextNode.class,
+ objectMapper.readTree("{"
+ + " \"attr1\": ["
+ + " {\"inAttr1\": \"val1a\", \"inAttr2\": {\"attr1\": \"val1\"}},"
+ + " {\"inAttr1\": \"val1a\", \"inAttr2\": {\"attr1\": \"val1\"}},"
+ + " {\"inAttr1\": \"val1b\", \"inAttr2\": \"val2c\"}"
+ + " ],"
+ + " \"attr2\": \"val2\""
+ + "}"))
+ );
+ }
+
+ private static DocumentContext cloneDocumentContext(DocumentContext documentContext) {
+ return JsonPath.using(documentContext.configuration()).parse(documentContext.jsonString());
+ }
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java
index 0267590a7..4b97be9f4 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/JacksonJsonNodeJsonProviderTest.java
@@ -2,14 +2,22 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
+import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingException;
+import java.nio.charset.StandardCharsets;
import org.junit.Test;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.List;
import java.util.UUID;
@@ -46,6 +54,13 @@ public void json_can_be_parsed() {
assertThat(node.get("string-property").asText()).isEqualTo("string-value");
}
+ @Test
+ public void bytes_json_can_be_parsed() {
+ ObjectNode node = using(JACKSON_JSON_NODE_CONFIGURATION).parseUtf8(JSON_DOCUMENT.getBytes(StandardCharsets.UTF_8))
+ .read("$");
+ assertThat(node.get("string-property").asText()).isEqualTo("string-value");
+ }
+
@Test
public void always_return_same_object() { // Test because of Bug #211
DocumentContext context = using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT);
@@ -55,7 +70,7 @@ public void always_return_same_object() { // Test because of Bug #211
context.put("$", "child", child1);
ObjectNode node2 = context.read("$");
ObjectNode child2 = context.read("$.child");
-
+
assertThat(node1).isSameAs(node2);
assertThat(child1).isSameAs(child2);
}
@@ -86,7 +101,6 @@ public void longs_are_unwrapped() {
assertThat(unwrapped).isEqualTo(node.asLong());
}
-
@Test
public void list_of_numbers() {
ArrayNode objs = using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price");
@@ -97,6 +111,72 @@ public void list_of_numbers() {
assertThat(objs.get(3).asDouble()).isEqualTo(22.99D);
}
+ ObjectMapper objectMapperDecimal = new ObjectMapper().configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
+ Configuration JACKSON_JSON_NODE_CONFIGURATION_DECIMAL = Configuration
+ .builder()
+ .mappingProvider(new JacksonMappingProvider())
+ .jsonProvider(new JacksonJsonNodeJsonProvider(objectMapperDecimal))
+ .build();
+
+ @Test
+ public void bigdecimals_are_unwrapped() {
+ final BigDecimal bd = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.valueOf(10.5));
+ final String json = "{\"bd-property\" : " + bd.toString() + "}";
+
+ JsonNode node = using(JACKSON_JSON_NODE_CONFIGURATION_DECIMAL).parse(json).read("$.bd-property");
+ BigDecimal val = using(JACKSON_JSON_NODE_CONFIGURATION_DECIMAL).parse(json).read("$.bd-property", BigDecimal.class);
+
+ assertThat(node.isBigDecimal()).isTrue();
+ assertThat(val).isEqualTo(bd);
+ assertThat(val).isEqualTo(node.decimalValue());
+ }
+
+ @Test
+ public void small_bigdecimals_are_unwrapped() {
+ final BigDecimal bd = BigDecimal.valueOf(10.5);
+ final String json = "{\"bd-property\" : " + bd.toString() + "}";
+
+ JsonNode node = using(JACKSON_JSON_NODE_CONFIGURATION_DECIMAL).parse(json).read("$.bd-property");
+ BigDecimal val = using(JACKSON_JSON_NODE_CONFIGURATION_DECIMAL).parse(json).read("$.bd-property", BigDecimal.class);
+
+ assertThat(node.isBigDecimal()).isTrue();
+ assertThat(val).isEqualTo(bd);
+ assertThat(val).isEqualTo(node.decimalValue());
+ }
+
+ ObjectMapper objectMapperBigInteger = new ObjectMapper().configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true);
+ Configuration JACKSON_JSON_NODE_CONFIGURATION_Big_Integer = Configuration
+ .builder()
+ .mappingProvider(new JacksonMappingProvider())
+ .jsonProvider(new JacksonJsonNodeJsonProvider(objectMapperBigInteger))
+ .build();
+
+ @Test
+ public void bigintegers_are_unwrapped() {
+ final BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.TEN);
+ final String json = "{\"bi-property\" : " + bi.toString() + "}";
+
+ JsonNode node = using(JACKSON_JSON_NODE_CONFIGURATION_Big_Integer).parse(json).read("$.bi-property");
+ BigInteger val = using(JACKSON_JSON_NODE_CONFIGURATION_Big_Integer).parse(json).read("$.bi-property", BigInteger.class);
+
+ assertThat(node.isBigInteger()).isTrue();
+ assertThat(val).isEqualTo(bi);
+ assertThat(val).isEqualTo(node.bigIntegerValue());
+ }
+
+ @Test
+ public void small_bigintegers_are_unwrapped() {
+ final BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE);
+ final String json = "{\"bi-property\" : " + bi.toString() + "}";
+
+ JsonNode node = using(JACKSON_JSON_NODE_CONFIGURATION_Big_Integer).parse(json).read("$.bi-property");
+ BigInteger val = using(JACKSON_JSON_NODE_CONFIGURATION_Big_Integer).parse(json).read("$.bi-property", BigInteger.class);
+
+ assertThat(node.isBigInteger()).isTrue();
+ assertThat(val).isEqualTo(bi);
+ assertThat(val).isEqualTo(node.bigIntegerValue());
+ }
+
@Test
public void test_type_ref() throws IOException {
TypeRef>> typeRef = new TypeRef>>() {};
@@ -112,7 +192,32 @@ public void test_type_ref_fail() throws IOException {
using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON).read("$", typeRef);
}
-
+
+ @Test
+ public void mapPropertyWithPOJO() {
+ String someJson = "" +
+ "{\n" +
+ " \"a\": \"a\",\n" +
+ " \"b\": \"b\"\n" +
+ "}";
+ ObjectMapper om = new ObjectMapper();
+ om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ Configuration c = Configuration
+ .builder()
+ .mappingProvider(new JacksonMappingProvider())
+ .jsonProvider(new JacksonJsonNodeJsonProvider(om))
+ .build();
+ DocumentContext context = JsonPath.using(c).parse(someJson);
+ String someJsonStr = context.jsonString();
+ DocumentContext altered = context.map("$['a', 'b', 'c']", new MapFunction() {
+ @Override
+ public Object map(Object currentValue, Configuration configuration) {
+ return currentValue;
+ }
+ });
+ assertThat(altered.jsonString()).isEqualTo(someJsonStr);
+ }
+
@Test
// https://github.com/json-path/JsonPath/issues/364
public void setPropertyWithPOJO() {
@@ -161,7 +266,7 @@ public static class FooBarBaz {
public static class Gen {
public String eric;
}
-
+
public static final class Data {
@JsonProperty("id")
UUID id;
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JacksonTest.java b/json-path/src/test/java/com/jayway/jsonpath/JacksonTest.java
index ef79e22a5..83cfe5b50 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/JacksonTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/JacksonTest.java
@@ -1,12 +1,13 @@
package com.jayway.jsonpath;
-import org.junit.Test;
-
import java.util.Date;
+import org.junit.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
+
public class JacksonTest extends BaseTest {
@Test
@@ -25,6 +26,12 @@ public void an_object_can_be_mapped_to_pojo() {
assertThat(fooBarBaz.bar).isEqualTo(10L);
assertThat(fooBarBaz.baz).isEqualTo(true);
+ fooBarBaz = JsonPath.using(JACKSON_CONFIGURATION).parseUtf8(json.getBytes(UTF_8))
+ .read("$", FooBarBaz.class);
+
+ assertThat(fooBarBaz.foo).isEqualTo("foo");
+ assertThat(fooBarBaz.bar).isEqualTo(10L);
+ assertThat(fooBarBaz.baz).isEqualTo(true);
}
public static class FooBarBaz {
@@ -52,7 +59,11 @@ public void single_quotes_work_with_in_filter() {
final Object readFromSingleQuote = JsonPath.using(JACKSON_CONFIGURATION).parse(jsonArray).read("$.[?(@.foo in ['bar'])].foo");
final Object readFromDoubleQuote = JsonPath.using(JACKSON_CONFIGURATION).parse(jsonArray).read("$.[?(@.foo in [\"bar\"])].foo");
assertThat(readFromSingleQuote).isEqualTo(readFromDoubleQuote);
-
+ final Object readFromSingleQuoteBytes = JsonPath.using(JACKSON_CONFIGURATION).parseUtf8(jsonArray.getBytes(UTF_8))
+ .read("$.[?(@.foo in ['bar'])].foo");
+ final Object readFromDoubleQuoteBytes = JsonPath.using(JACKSON_CONFIGURATION).parseUtf8(jsonArray.getBytes(UTF_8))
+ .read("$.[?(@.foo in [\"bar\"])].foo");
+ assertThat(readFromSingleQuoteBytes).isEqualTo(readFromDoubleQuoteBytes);
}
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JakartaJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JakartaJsonProviderTest.java
new file mode 100644
index 000000000..967c2957e
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/JakartaJsonProviderTest.java
@@ -0,0 +1,390 @@
+package com.jayway.jsonpath;
+
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+import static com.jayway.jsonpath.JsonPath.parse;
+import static com.jayway.jsonpath.JsonPath.using;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.emptyMap;
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class JakartaJsonProviderTest extends BaseTest {
+
+ private static final Map EMPTY_MAP = emptyMap();
+
+ @Test
+ public void an_object_can_be_read() {
+ JsonObject book = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .read("$.store.book[0]");
+
+ assertThat(((JsonString) book.get("author")).getChars()).isEqualTo("Nigel Rees");
+ }
+
+ @Test
+ public void an_object_can_be_read_from_bytes() {
+ JsonObject book = using(JAKARTA_JSON_CONFIGURATION)
+ .parseUtf8(JSON_DOCUMENT.getBytes(UTF_8))
+ .read("$.store.book[0]");
+
+ assertThat(((JsonString) book.get("author")).getChars()).isEqualTo("Nigel Rees");
+ }
+
+ @Test
+ public void a_property_can_be_read() {
+ JsonString category = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .read("$.store.book[0].category");
+
+ assertThat(category.getString()).isEqualTo("reference");
+ }
+
+ @Test
+ public void a_filter_can_be_applied() {
+ List fictionBooks = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .read("$.store.book[?(@.category == 'fiction')]");
+
+ assertThat(fictionBooks.size()).isEqualTo(3);
+ }
+
+ @Test
+ public void result_can_be_mapped_to_object() {
+ @SuppressWarnings("unchecked")
+ List> books = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .read("$.store.book", List.class);
+
+ assertThat(books.size()).isEqualTo(4);
+ }
+
+ @Test
+ public void read_books_with_isbn() {
+ List books = using(JAKARTA_JSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$..book[?(@.isbn)]");
+
+ assertThat(books.size()).isEqualTo(2);
+ }
+
+ /**
+ * Functions take parameters, the length parameter for example takes an entire document which we anticipate
+ * will compute to a document that is an array of elements which can determine its length.
+ *
+ * Since we translate this query from $..books.length() to length($..books) verify that this particular translation
+ * works as anticipated.
+ */
+ @Test
+ public void read_book_length_using_translated_query() {
+ Integer result = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_BOOK_STORE_DOCUMENT)
+ .read("$..book.length()");
+ assertThat(result).isEqualTo(4);
+ }
+
+ @Test
+ public void read_book_length() {
+ Object result = using(JAKARTA_JSON_CONFIGURATION)
+ .parse(JSON_BOOK_STORE_DOCUMENT)
+ .read("$.length($..book)");
+ assertThat(result).isEqualTo(4);
+ }
+
+ @Test
+ public void issue_97() {
+ String json = "{ \"books\": [ " +
+ "{ \"category\": \"fiction\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"fiction\" }, " +
+ "{ \"category\": \"fiction\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"fiction\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"reference\" }, " +
+ "{ \"category\": \"reference\" } ] }";
+
+ DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(json)
+ .delete("$.books[?(@.category == 'reference')]");
+ //System.out.println((Object) dc.read("$"));
+ @SuppressWarnings("unchecked")
+ List categories = dc.read("$..category", List.class);
+
+ assertThat(categories).containsOnly("fiction");
+ }
+
+ @Test
+ public void test_delete_2() {
+ DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse("[" +
+ "{\"top\": {\"middle\": null}}," +
+ "{\"top\": {\"middle\": {} }}," +
+ "{\"top\": {\"middle\": {\"bottom\": 2} }}" +
+ "]")
+ .delete(JsonPath.compile("$[*].top.middle.bottom"));
+ Object ans = dc.read("$");
+ //System.out.println(ans);
+ assert(ans.toString().equals("[{\"top\":{\"middle\":null}},{\"top\":{\"middle\":{}}},{\"top\":{\"middle\":{}}}]"));
+ }
+
+ @Test
+ public void an_root_property_can_be_updated() {
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$.int-max-property", 1)
+ .json();
+
+ Integer result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read("$.int-max-property", Integer.class);
+
+ assertThat(result).isEqualTo(1);
+ }
+
+ @Test
+ public void an_deep_scan_can_update() {
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$..display-price", 1)
+ .json();
+
+ List result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read("$..display-price", new TypeRef>() {});
+
+ assertThat(result).containsExactly(1, 1, 1, 1, 1);
+ }
+
+ @Test
+ public void an_filter_can_update() {
+ final String updatePathFunction = "$.store.book[?(@.display-price)].display-price";
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set(updatePathFunction, 1)
+ .json();
+
+ List result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read(updatePathFunction, new TypeRef>() {});
+
+ assertThat(result).containsExactly(1, 1, 1, 1);
+ }
+
+ @Test
+ public void a_path_can_be_deleted() {
+ final String deletePath = "$.store.book[*].display-price";
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .delete(deletePath)
+ .json();
+
+ List result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read(deletePath, new TypeRef>() {});
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void operations_can_chained() {
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .delete("$.store.book[*].display-price")
+ .set("$.store.book[*].category", "A")
+ .json();
+
+ List prices = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read("$.store.book[*].display-price", new TypeRef>() {});
+ List categories = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o)
+ .read("$.store.book[*].category", new TypeRef>() {});
+
+ assertThat(prices).isEmpty();
+ assertThat(categories).containsExactly("A", "A", "A", "A");
+ }
+
+ @Test
+ public void an_array_index_can_be_updated() {
+ String res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$.store.book[0]", "a")
+ .read("$.store.book[0]", String.class);
+
+ assertThat(res).isEqualTo("a");
+ }
+
+ @Test
+ public void an_array_slice_can_be_updated() {
+ List res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$.store.book[0:2]", "a")
+ .read("$.store.book[0:2]", new TypeRef>() {});
+
+ assertThat(res).containsExactly("a", "a");
+ }
+
+ @Test
+ public void an_array_criteria_can_be_updated() {
+ List res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$.store.book[?(@.category == 'fiction')]", "a")
+ .read("$.store.book[?(@ == 'a')]", new TypeRef>() {});
+
+ assertThat(res).containsExactly("a", "a", "a");
+ }
+
+ @Test
+ public void an_array_criteria_can_be_deleted() {
+ List res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .delete("$.store.book[?(@.category == 'fiction')]")
+ .read("$.store.book[*].category", new TypeRef>() {});
+
+ assertThat(res).containsExactly("reference");
+ }
+
+ @Test
+ public void an_array_criteria_with_multiple_results_can_be_deleted(){
+ InputStream stream = this.getClass().getResourceAsStream("/json_array_multiple_delete.json");
+ String deletePath = "$._embedded.mandates[?(@.count=~/0/)]";
+ DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION).parse(stream).delete(deletePath);
+ List result = dc.read(deletePath);
+ assertThat(result.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void multi_prop_delete() {
+ List> res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .delete("$.store.book[*]['author', 'category']")
+ .read("$.store.book[*]['author', 'category']", new TypeRef>>() {});
+
+ assertThat(res).containsExactly(EMPTY_MAP, EMPTY_MAP, EMPTY_MAP, EMPTY_MAP);
+ }
+
+ @Test
+ public void multi_prop_update() {
+ @SuppressWarnings("serial")
+ Map expected = new HashMap(){{
+ put("author", "a");
+ put("category", "a");
+ }};
+ List> res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .set("$.store.book[*]['author', 'category']", "a")
+ .read("$.store.book[*]['author', 'category']", new TypeRef>>() {});
+ assertThat(res).containsExactly(expected, expected, expected, expected);
+ }
+
+ @Test
+ public void add_to_array() {
+ Object res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .add("$.store.book", 1)
+ .read("$.store.book[4]");
+ res = JAKARTA_JSON_RW_CONFIGURATION.jsonProvider().unwrap(res);
+ assertThat(res).isEqualTo(1);
+ }
+
+ @Test
+ public void add_to_object() {
+ Object res = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .put("$.store.book[0]", "new-key", "new-value")
+ .read("$.store.book[0].new-key");
+ res = JAKARTA_JSON_RW_CONFIGURATION.jsonProvider().unwrap(res);
+ assertThat(res).isEqualTo("new-value");
+ }
+
+ @Test(expected = InvalidModificationException.class)
+ public void add_to_object_on_array() {
+ using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT).put("$.store.book", "new-key", "new-value");
+ }
+
+ @Test(expected = InvalidModificationException.class)
+ public void add_to_array_on_object() {
+ using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT).add("$.store.book[0]", "new-value");
+ }
+
+ @Test
+ public void a_path_can_be_renamed(){
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .renameKey("$.store", "book", "updated-book")
+ .json();
+ List result = parse(o).read("$.store.updated-book");
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ public void map_array_items_can_be_renamed(){
+ Object o = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .renameKey("$.store.book[*]", "category", "renamed-category")
+ .json();
+ List result = parse(o).read("$.store.book[*].renamed-category");
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test(expected = PathNotFoundException.class)
+ public void non_existent_key_rename_not_allowed() {
+ using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .renameKey("$", "fake", "new-fake")
+ .json();
+ }
+
+ @Test
+ public void single_match_value_can_be_mapped() {
+ MapFunction mapFunction = new ToStringMapFunction();
+ String stringResult = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .map("$.string-property", mapFunction)
+ .read("$.string-property", String.class);
+ assertThat(stringResult.endsWith("converted")).isTrue();
+ }
+
+ @Test
+ public void object_can_be_mapped() {
+ TypeRef> typeRef = new TypeRef>() {};
+ MapFunction mapFunction = new ToStringMapFunction();
+ DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT);
+ Object list = dc.read("$..book");
+ assertThat(list).isInstanceOf(List.class);
+ Object res = dc.map("$..book", mapFunction).read("$..book", typeRef).get(0);
+ assertThat(res).isInstanceOf(String.class);
+ assertThat((String) res).endsWith("converted");
+ }
+
+ @Test
+ public void multi_match_path_can_be_mapped() {
+ MapFunction mapFunction = new ToStringMapFunction();
+ List doubleResult = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .read("$..display-price", new TypeRef>() {});
+ for (Double dRes : doubleResult){
+ assertThat(dRes).isInstanceOf(Double.class);
+ }
+ List stringResult = using(JAKARTA_JSON_RW_CONFIGURATION)
+ .parse(JSON_DOCUMENT)
+ .map("$..display-price", mapFunction)
+ .read("$..display-price", new TypeRef>() {});
+ for (String sRes : stringResult){
+ assertThat(sRes).isInstanceOf(String.class);
+ assertThat(sRes.endsWith("converted")).isTrue();
+ }
+ }
+
+ // Helper converter implementation for test cases.
+ private class ToStringMapFunction implements MapFunction {
+
+ @Override
+ public Object map(Object currentValue, Configuration configuration) {
+ return currentValue.toString()+"converted";
+ }
+ }
+
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
index 59ba665c6..8600de888 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
@@ -1,5 +1,7 @@
package com.jayway.jsonpath;
+import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider;
+import com.jayway.jsonpath.spi.mapper.JsonOrgMappingProvider;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
@@ -52,4 +54,47 @@ public void read_books_with_isbn() {
assertThat(books.length()).isEqualTo(2);
}
+
+ /**
+ * Functions take parameters, the length parameter for example takes an entire document which we anticipate
+ * will compute to a document that is an array of elements which can determine its length.
+ *
+ * Since we translate this query from $..books.length() to length($..books) verify that this particular translation
+ * works as anticipated.
+ */
+ @Test
+ public void read_book_length_using_translated_query() {
+ Integer result = using(Configuration.defaultConfiguration())
+ .parse(JSON_BOOK_STORE_DOCUMENT)
+ .read("$..book.length()");
+ assertThat(result).isEqualTo(4);
+ }
+
+ @Test
+ public void read_book_length() {
+ Object result = using(Configuration.defaultConfiguration())
+ .parse(JSON_BOOK_STORE_DOCUMENT)
+ .read("$.length($..book)");
+ assertThat(result).isEqualTo(4);
+ }
+
+ @Test
+ public void test_getPropertyKeys_empty_object() {
+ String json = "{\"foo\": \"bar\", \"emptyObject\": {},\"emptyList\":[]}";
+ Configuration config = Configuration.defaultConfiguration()
+ .jsonProvider(new JsonOrgJsonProvider())
+ .mappingProvider(new JsonOrgMappingProvider());
+ Object result = JsonPath.using(config).parse(json).read("$..foo");
+ assertThat(result.toString()).isEqualTo("[\"bar\"]");
+ }
+
+ @Test
+ public void test_getPropertyKeys_empty_nest_object() {
+ String json = "{\"foo\": \"bar\", \"emptyObject\": {\"emptyList\":[]},\"emptyList\":[]}";
+ Configuration config = Configuration.defaultConfiguration()
+ .jsonProvider(new JsonOrgJsonProvider())
+ .mappingProvider(new JsonOrgMappingProvider());
+ Object result = JsonPath.using(config).parse(json).read("$..foo");
+ assertThat(result.toString()).isEqualTo("[\"bar\"]");
+ }
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java
index fc5567a89..de5824b96 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/MultiPropTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import static com.jayway.jsonpath.JsonPath.using;
@@ -114,7 +115,7 @@ public void non_leaf_multi_props_can_be_required() {
final Configuration conf = Configuration.defaultConfiguration().addOptions(Option.REQUIRE_PROPERTIES);
final String json = "{\"a\": {\"v\": 5}, \"b\": {\"v\": 4}, \"c\": {\"v\": 1}}";
- assertThat(using(conf).parse(json).read("$['a', 'c'].v")).asList().containsOnly(5, 1);
+ assertThat((List)using(conf).parse(json).read("$['a', 'c'].v")).asList().containsOnly(5, 1);
assertEvaluationThrows(json, "$['d', 'a', 'c', 'm'].v", PathNotFoundException.class, conf);
}
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java
index 1093ff7aa..873b3b6af 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java
@@ -21,7 +21,7 @@ public void a_leafs_is_not_defaulted_to_null() {
Configuration conf = Configuration.defaultConfiguration();
- assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.baz")).isNull();
+ assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.baz")).isNull();
}
@Test
@@ -37,7 +37,7 @@ public void a_definite_path_is_not_returned_as_list_by_default() {
Configuration conf = Configuration.defaultConfiguration();
- assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(String.class);
+ assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(String.class);
}
@Test
@@ -45,11 +45,11 @@ public void a_definite_path_can_be_returned_as_list() {
Configuration conf = Configuration.builder().options(ALWAYS_RETURN_LIST).build();
- assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(List.class);
+ assertThat((List)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isInstanceOf(List.class);
- assertThat(using(conf).parse("{\"foo\": null}").read("$.foo")).isInstanceOf(List.class);
+ assertThat((List)using(conf).parse("{\"foo\": null}").read("$.foo")).isInstanceOf(List.class);
- assertThat(using(conf).parse("{\"foo\": [1, 4, 8]}").read("$.foo")).asList()
+ assertThat((List)using(conf).parse("{\"foo\": [1, 4, 8]}").read("$.foo")).asList()
.containsExactly(Arrays.asList(1, 4, 8));
}
@@ -61,7 +61,7 @@ public void an_indefinite_path_can_be_returned_as_list() {
assertThat(result).hasSize(1);
assertThat(result.get(0)).isNull();
- assertThat(using(conf).parse("{\"bar\": {\"foo\": [1, 4, 8]}}").read("$..foo")).asList()
+ assertThat((List)using(conf).parse("{\"bar\": {\"foo\": [1, 4, 8]}}").read("$..foo")).asList()
.containsExactly(Arrays.asList(1, 4, 8));
}
@@ -69,7 +69,7 @@ public void an_indefinite_path_can_be_returned_as_list() {
public void a_path_evaluation_is_returned_as_VALUE_by_default() {
Configuration conf = Configuration.defaultConfiguration();
- assertThat(using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isEqualTo("bar");
+ assertThat((String)using(conf).parse("{\"foo\" : \"bar\"}").read("$.foo")).isEqualTo("bar");
}
@Test
@@ -142,13 +142,13 @@ public void when_property_is_required_exception_is_thrown_2() {
public void issue_suppress_exceptions_does_not_break_indefinite_evaluation() {
Configuration conf = Configuration.builder().options(SUPPRESS_EXCEPTIONS).build();
- assertThat(using(conf).parse("{\"foo2\": [5]}").read("$..foo2[0]")).asList().containsOnly(5);
- assertThat(using(conf).parse("{\"foo\" : {\"foo2\": [5]}}").read("$..foo2[0]")).asList().containsOnly(5);
- assertThat(using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo2[0]")).asList().containsOnly(5);
+ assertThat((List)using(conf).parse("{\"foo2\": [5]}").read("$..foo2[0]")).asList().containsOnly(5);
+ assertThat((List)using(conf).parse("{\"foo\" : {\"foo2\": [5]}}").read("$..foo2[0]")).asList().containsOnly(5);
+ assertThat((List)using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo2[0]")).asList().containsOnly(5);
- assertThat(using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo.foo2[0]")).asList().containsOnly(5);
+ assertThat((List)using(conf).parse("[null, [{\"foo\" : {\"foo2\": [5]}}]]").read("$..foo.foo2[0]")).asList().containsOnly(5);
- assertThat(using(conf).parse("{\"aoo\" : {}, \"foo\" : {\"foo2\": [5]}, \"zoo\" : {}}").read("$[*].foo2[0]")).asList().containsOnly(5);
+ assertThat((List)using(conf).parse("{\"aoo\" : {}, \"foo\" : {\"foo2\": [5]}, \"zoo\" : {}}").read("$[*].foo2[0]")).asList().containsOnly(5);
}
@Test
diff --git a/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java b/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java
index 699ba631b..ef2206191 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/PathCompilerTest.java
@@ -1,5 +1,6 @@
package com.jayway.jsonpath;
+import com.jayway.jsonpath.internal.ParseContextImpl;
import org.junit.Ignore;
import org.junit.Test;
@@ -236,6 +237,49 @@ public void issue_predicate_or_has_lower_priority_than_and() {
assertThat(result).hasSize(1);
}
+ @Test
+ public void issue_predicate_can_have_double_quotes() {
+ String json = "{\n"
+ + " \"logs\": [\n"
+ + " {\n"
+ + " \"message\": \"\\\"it\\\"\",\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ List result = JsonPath.read(json, "$.logs[?(@.message == '\"it\"')].message");
+ assertThat(result).containsExactly("\"it\"");
+ }
+
+ @Test
+ public void issue_predicate_can_have_single_quotes() {
+ String json = "{\n"
+ + " \"logs\": [\n"
+ + " {\n"
+ + " \"message\": \"'it'\",\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ DocumentContext parse = JsonPath.parse(json);
+ JsonPath compile = JsonPath.compile("$.logs[?(@.message == \"'it'\")].message");
+ List result = parse.read(compile);
+ assertThat(result).containsExactly("'it'");
+ }
+
+ @Test
+ public void issue_predicate_can_have_single_quotes_escaped() {
+ String json = "{\n"
+ + " \"logs\": [\n"
+ + " {\n"
+ + " \"message\": \"'it'\",\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ DocumentContext parse = JsonPath.parse(json);
+ JsonPath compile = JsonPath.compile("$.logs[?(@.message == '\\'it\\'')].message");
+ List