Browse Source

Merge 84c93f9e81 into 45333e0a31

pull/1008/merge
vaibhavramchandani 2 months ago committed by GitHub
parent
commit
461cde1a48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 33
      json-path/src/main/java/com/jayway/jsonpath/InvalidCriteriaException.java
  2. 1
      json-path/src/main/java/com/jayway/jsonpath/JsonPathException.java
  3. 13
      json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
  4. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java
  5. 20
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
  6. 46
      json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
  7. 98
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  8. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  9. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java
  10. 40
      json-path/src/main/java/com/jayway/jsonpath/internal/path/ScannerFunctionInverter.java
  11. 57
      json-path/src/test/java/com/jayway/jsonpath/Configurations.java
  12. 61
      json-path/src/test/java/com/jayway/jsonpath/internal/JsonFormatterTest.java
  13. 3
      json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java
  14. 3
      json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java
  15. 2
      json-path/src/test/java/com/jayway/jsonpath/internal/function/KeySetFunctionTest.java
  16. 2
      json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java

33
json-path/src/main/java/com/jayway/jsonpath/InvalidCriteriaException.java

@ -1,33 +0,0 @@
/*
* 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;
@SuppressWarnings("serial")
public class InvalidCriteriaException extends JsonPathException {
public InvalidCriteriaException() {
}
public InvalidCriteriaException(String message) {
super(message);
}
public InvalidCriteriaException(String message, Throwable cause) {
super(message, cause);
}
public InvalidCriteriaException(Throwable cause) {
super(cause);
}
}

1
json-path/src/main/java/com/jayway/jsonpath/JsonPathException.java

@ -17,6 +17,7 @@ package com.jayway.jsonpath;
public class JsonPathException extends RuntimeException {
public JsonPathException() {
super();
}
public JsonPathException(String message) {

13
json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java

@ -3,7 +3,6 @@ package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.InvalidPathException;
public class CharacterIndex {
private static final char OPEN_PARENTHESIS = '(';
private static final char CLOSE_PARENTHESIS = ')';
private static final char CLOSE_SQUARE_BRACKET = ']';
@ -29,6 +28,7 @@ public class CharacterIndex {
this.endPosition = charSequence.length() - 1;
}
public int length() {
return endPosition + 1;
}
@ -320,4 +320,15 @@ public class CharacterIndex {
skipBlanksAtEnd();
return this;
}
public void readWhitespace() {
while (inBounds()) {
char c = currentChar();
if (!Character.isWhitespace(c)) {
break;
}
incrementPosition(1);
}
}
}

5
json-path/src/main/java/com/jayway/jsonpath/internal/Utils.java

@ -175,14 +175,15 @@ public final class Utils {
}
int len = str.length();
StringWriter writer = new StringWriter(len);
StringBuilder unicode = new StringBuilder(4);
final int UNICODE_LENGTH = 4;
StringBuilder unicode = new StringBuilder(UNICODE_LENGTH);
boolean hadSlash = false;
boolean inUnicode = false;
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
if (inUnicode) {
unicode.append(ch);
if (unicode.length() == 4) {
if (unicode.length() == UNICODE_LENGTH) {
try {
int value = Integer.parseInt(unicode.toString(), 16);
writer.write((char) value);

20
json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java

@ -205,23 +205,28 @@ public class FilterCompiler {
return new RelationalExpressionNode(left, operator, right);
}
private LogicalOperator readLogicalOperator(){
int begin = filter.skipBlanks().position();
int end = begin+1;
private LogicalOperator readLogicalOperator(CharSequence charSequence, int position) {
int begin = position;
int end = begin + 1;
if(!filter.inBounds(end)){
if (!inBounds(charSequence, end)) {
throw new InvalidPathException("Expected boolean literal");
}
CharSequence logicalOperator = filter.subSequence(begin, end+1);
if(!logicalOperator.equals("||") && !logicalOperator.equals("&&")){
CharSequence logicalOperator = charSequence.subSequence(begin, end + 1);
if (!logicalOperator.equals("||") && !logicalOperator.equals("&&")) {
throw new InvalidPathException("Expected logical operator");
}
filter.incrementPosition(logicalOperator.length());
logger.trace("LogicalOperator from {} to {} -> [{}]", begin, end, logicalOperator);
return LogicalOperator.fromString(logicalOperator.toString());
}
private boolean inBounds(CharSequence charSequence, int index) {
return index >= 0 && index < charSequence.length();
}
private RelationalOperator readRelationalOperator() {
int begin = filter.skipBlanks().position();
@ -379,6 +384,7 @@ public class FilterCompiler {
return ValueNode.createPathNode(path, false, shouldExists);
}
private boolean expressionIsTerminated(){
char c = filter.currentChar();
if(c == CLOSE_PARENTHESIS || isLogicalOperatorChar(c)){

46
json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java

@ -24,8 +24,10 @@ 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);
@ -36,7 +38,8 @@ public class CompiledPath implements Path {
public CompiledPath(RootPathToken root, boolean isRootPath) {
this.root = invertScannerFunctionRelationship(root);
ScannerFunctionInverter inverter = new ScannerFunctionInverter(root);
this.root = inverter.invert();
this.isRootPath = isRootPath;
}
@ -47,45 +50,8 @@ public class CompiledPath implements Path {
/**
* 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) {

98
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java

@ -85,13 +85,7 @@ public class PathCompiler {
}
private void readWhitespace() {
while (path.inBounds()) {
char c = path.currentChar();
if (!isWhitespace(c)) {
break;
}
path.incrementPosition(1);
}
path.readWhitespace();
}
private Boolean isPathContext(char c) {
@ -101,7 +95,7 @@ public class PathCompiler {
//[$ | @]
private RootPathToken readContextToken() {
readWhitespace();
path.readWhitespace();
if (!isPathContext(path.currentChar())) {
throw new InvalidPathException("Path must start with '$' or '@'");
@ -305,7 +299,7 @@ public class PathCompiler {
continue;
}
if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c || MINUS == c) {
if (isJsonParam(c)) {
type = ParamType.JSON;
}
else if (isPathContext(c)) {
@ -394,6 +388,26 @@ public class PathCompiler {
return parameters;
}
private boolean isOpenBrace(char c) {
return c == OPEN_BRACE;
}
private boolean isDigit(char c) {
return Character.isDigit(c);
}
private boolean isDoubleQuote(char c) {
return c == DOUBLE_QUOTE;
}
private boolean isMinus(char c) {
return c == MINUS;
}
private boolean isJsonParam(char c) {
return isOpenBrace(c) || isDigit(c) || isDoubleQuote(c) || isMinus(c);
}
private boolean isWhitespace(char c) {
return (c == SPACE || c == TAB || c == LF || c == CR);
}
@ -519,12 +533,11 @@ public class PathCompiler {
// [1], [1,2, n], [1:], [1:2], [:2]
//
private boolean readArrayToken(PathTokenAppender appender) {
if (!path.currentCharIs(OPEN_SQUARE_BRACKET)) {
if (!isOpeningSquareBracket()) {
return false;
}
char nextSignificantChar = path.nextSignificantChar();
if (!isDigit(nextSignificantChar) && nextSignificantChar != MINUS && nextSignificantChar != SPLIT) {
char nextSignificantChar = getNextSignificantChar();
if (!isValidNextChar(nextSignificantChar)) {
return false;
}
@ -535,28 +548,18 @@ public class PathCompiler {
return false;
}
String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString().trim();
String expression = extractExpression(expressionBeginIndex, expressionEndIndex);
if ("*".equals(expression)) {
if (!isValidExpression(expression)) {
return false;
}
//check valid chars
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (!isDigit(c) && c != COMMA && c != MINUS && c != SPLIT && c != SPACE) {
return false;
}
}
boolean isSliceOperation = expression.contains(":");
if (isSliceOperation) {
ArraySliceOperation arraySliceOperation = ArraySliceOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createSliceArrayPathToken(arraySliceOperation));
processSliceOperation(expression, appender);
} else {
ArrayIndexOperation arrayIndexOperation = ArrayIndexOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createIndexArrayPathToken(arrayIndexOperation));
processIndexOperation(expression, appender);
}
path.setPosition(expressionEndIndex + 1);
@ -564,6 +567,47 @@ public class PathCompiler {
return path.currentIsTail() || readNextToken(appender);
}
private boolean isOpeningSquareBracket() {
return path.currentCharIs(OPEN_SQUARE_BRACKET);
}
private char getNextSignificantChar() {
return path.nextSignificantChar();
}
private boolean isValidNextChar(char nextChar) {
return isDigit(nextChar) || nextChar == MINUS || nextChar == SPLIT;
}
private String extractExpression(int beginIndex, int endIndex) {
return path.subSequence(beginIndex, endIndex).toString().trim();
}
private boolean isValidExpression(String expression) {
return !"*".equals(expression) && containsValidChars(expression);
}
private boolean containsValidChars(String expression) {
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (!isDigit(c) && c != COMMA && c != MINUS && c != SPLIT && c != SPACE) {
return false;
}
}
return true;
}
private void processSliceOperation(String expression, PathTokenAppender appender) {
ArraySliceOperation arraySliceOperation = ArraySliceOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createSliceArrayPathToken(arraySliceOperation));
}
private void processIndexOperation(String expression, PathTokenAppender appender) {
ArrayIndexOperation arrayIndexOperation = ArrayIndexOperation.parse(expression);
appender.appendPathToken(PathTokenFactory.createIndexArrayPathToken(arrayIndexOperation));
}
//
// ['foo']
//

2
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java

@ -150,7 +150,7 @@ public abstract class PathToken {
return prev;
}
PathToken next() {
public PathToken next() {
if (isLeaf()) {
throw new IllegalStateException("Current path token is a leaf");
}

2
json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java

@ -26,7 +26,7 @@ public class RootPathToken extends PathToken {
private final String rootToken;
RootPathToken(char rootToken) {
public RootPathToken(char rootToken) {
this.rootToken = Character.toString(rootToken);
this.tail = this;
this.tokenCount = 1;

40
json-path/src/main/java/com/jayway/jsonpath/internal/path/ScannerFunctionInverter.java

@ -0,0 +1,40 @@
package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.internal.function.ParamType;
import com.jayway.jsonpath.internal.function.Parameter;
import java.util.Arrays;
public class ScannerFunctionInverter {
private final RootPathToken path;
public ScannerFunctionInverter(RootPathToken path) {
this.path = path;
}
public RootPathToken invert() {
if (path.isFunctionPath() && path.next() instanceof ScanPathToken) {
PathToken token = path;
PathToken prior = null;
while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) {
prior = token;
}
if (token instanceof FunctionPathToken) {
prior.setNext(null);
path.setTail(prior);
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);
return functionRoot;
}
}
return path;
}
}

57
json-path/src/test/java/com/jayway/jsonpath/Configurations.java

@ -16,37 +16,37 @@ import java.util.Arrays;
public class Configurations {
public static final Configuration JSON_ORG_CONFIGURATION = Configuration
private static final Configuration JSON_ORG_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JsonOrgMappingProvider())
.jsonProvider(new JsonOrgJsonProvider())
.build();
public static final Configuration GSON_CONFIGURATION = Configuration
private static final Configuration GSON_CONFIGURATION = Configuration
.builder()
.mappingProvider(new GsonMappingProvider())
.jsonProvider(new GsonJsonProvider())
.build();
public static final Configuration JACKSON_CONFIGURATION = Configuration
private static final Configuration JACKSON_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JacksonMappingProvider())
.jsonProvider(new JacksonJsonProvider())
.build();
public static final Configuration JACKSON_JSON_NODE_CONFIGURATION = Configuration
private static final Configuration JACKSON_JSON_NODE_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JacksonMappingProvider())
.jsonProvider(new JacksonJsonNodeJsonProvider())
.build();
public static final Configuration JSON_SMART_CONFIGURATION = Configuration
private static final Configuration JSON_SMART_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JsonSmartMappingProvider())
.jsonProvider(new JsonSmartJsonProvider())
.build();
public static final Configuration JAKARTA_CONFIGURATION = Configuration
private static final Configuration JAKARTA_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JakartaMappingProvider())
.jsonProvider(new JakartaJsonProvider())
@ -54,21 +54,46 @@ public class Configurations {
public static Iterable<Configuration> configurations() {
return Arrays.asList(
JSON_SMART_CONFIGURATION
,GSON_CONFIGURATION
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
,JSON_ORG_CONFIGURATION
,JAKARTA_CONFIGURATION
JSON_SMART_CONFIGURATION,
GSON_CONFIGURATION,
JACKSON_CONFIGURATION,
JACKSON_JSON_NODE_CONFIGURATION,
JSON_ORG_CONFIGURATION,
JAKARTA_CONFIGURATION
);
}
public static Iterable<Configuration> objectMappingConfigurations() {
return Arrays.asList(
GSON_CONFIGURATION
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
,JAKARTA_CONFIGURATION
GSON_CONFIGURATION,
JACKSON_CONFIGURATION,
JACKSON_JSON_NODE_CONFIGURATION,
JAKARTA_CONFIGURATION
);
}
// Public getter methods for accessing configurations
public static Configuration getJsonOrgConfiguration() {
return JSON_ORG_CONFIGURATION;
}
public static Configuration getGsonConfiguration() {
return GSON_CONFIGURATION;
}
public static Configuration getJacksonConfiguration() {
return JACKSON_CONFIGURATION;
}
public static Configuration getJacksonJsonNodeConfiguration() {
return JACKSON_JSON_NODE_CONFIGURATION;
}
public static Configuration getJsonSmartConfiguration() {
return JSON_SMART_CONFIGURATION;
}
public static Configuration getJakartaConfiguration() {
return JAKARTA_CONFIGURATION;
}
}

61
json-path/src/test/java/com/jayway/jsonpath/internal/JsonFormatterTest.java

@ -0,0 +1,61 @@
package com.jayway.jsonpath.internal;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonFormatterTest {
// Test case for pretty printing a simple JSON object
// The input is a simple JSON object
// The expected output is the same JSON object with proper indentation
@Test
public void testPrettyPrint_SimpleJson() {
JsonFormatter jsonFormatter = new JsonFormatter();
String input = "{\"name\":\"Vaibhav Ramchandani\",\"age\":23,\"city\":\"Halifax\"}";
String actualOutput = jsonFormatter.prettyPrint(input);
String expectedOutput = "{\n \"name\" : \"Vaibhav Ramchandani\",\n \"age\" : 23,\n \"city\" : \"Halifax\"\n}";
assertEquals(expectedOutput.replaceAll("\\s", ""), actualOutput.replaceAll("\\s", ""));
}
// Test case for pretty printing a JSON object with escaped characters
// The input is a JSON object with escaped characters
// The expected output is the same JSON object with proper indentation
//
@Test
public void testPrettyPrint_JsonWithEscapedCharacters() {
JsonFormatter jsonFormatter = new JsonFormatter();
String input = "{\"message\":\"This is ,\\nJsonPath Repo!\"}";
String actualOutput = jsonFormatter.prettyPrint(input);
String expectedOutput = "{\n \"message\" : \"This is ,\\nJsonPath Repo!\"\n}";
assertEquals(expectedOutput.replaceAll("\\s", ""), actualOutput.replaceAll("\\s", ""));
}
// Test case for pretty printing a JSON array
// The input is a JSON array
// The expected output is the same JSON array with proper indentation
@Test
public void testPrettyPrint_JsonWithArray() {
//
JsonFormatter jsonFormatter = new JsonFormatter();
String input = "[{\"name\":\"Vaibhav R\",\"age\":23},{\"name\":\"Sanskar K\",\"age\":24}]";
String actualOutput=jsonFormatter.prettyPrint(input);
String expectedOutput = "[\n {\n \"name\" : \"Vaibhav R\",\n \"age\" : 23\n },\n {\n \"name\" : \"Sanskar K\",\n \"age\" : 24\n }\n]";
assertEquals(expectedOutput.replaceAll("\\s", ""), actualOutput.replaceAll("\\s", ""));
}
// Test case for pretty printing a JSON object with single quotes
// The input is a JSON object with single quotes
// The expected output is the same JSON object with proper indentation
@Test
public void testPrettyPrint_JsonWithSingleQuotes() {
JsonFormatter jsonFormatter = new JsonFormatter();
String input = "{'name':'Vaibhav Ramchandani','age':23,'city':'Halifax'}";
String actualOutput = jsonFormatter.prettyPrint(input);
String expectedOutput = "{\n 'name' : 'Vaibhav Ramchandani',\n 'age' : 23,\n 'city' : 'Halifax'\n}";
assertEquals(expectedOutput.replaceAll("\\s", ""), actualOutput.replaceAll("\\s", ""));
}
}

3
json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java

@ -16,7 +16,8 @@ import java.io.InputStream;
*/
public class Issue191 {
private Configuration conf = Configurations.GSON_CONFIGURATION;
private Configuration conf = Configurations.getGsonConfiguration();
@Test
public void testResultSetNumericComputation() {

3
json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java

@ -52,7 +52,8 @@ public class JSONEntityPathFunctionTest extends BaseFunctionTest {
private Configuration conf = Configurations.JSON_SMART_CONFIGURATION;
private Configuration conf= Configurations.getJsonSmartConfiguration();
@Test
public void testLengthOfTextArray() {

2
json-path/src/test/java/com/jayway/jsonpath/internal/function/KeySetFunctionTest.java

@ -14,7 +14,7 @@ import java.util.HashSet;
*/
public class KeySetFunctionTest extends BaseFunctionTest {
private Configuration conf = Configurations.JACKSON_CONFIGURATION;
private Configuration conf = Configurations.getJsonSmartConfiguration();
@Test
public void testKeySet() throws Exception {

2
json-path/src/test/java/com/jayway/jsonpath/internal/function/SequentialPathFunctionTest.java

@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test;
*/
public class SequentialPathFunctionTest extends BaseFunctionTest {
private Configuration conf = Configurations.JACKSON_CONFIGURATION;
private Configuration conf = Configurations.getJsonSmartConfiguration();
@Test
public void testFirstOfNumbers() throws Exception {

Loading…
Cancel
Save