Browse Source

When the path parameters to a length function for $..book.length() is translated to $.length($..book) its really taking the length of the structure pointed at by the result set $..book not $..book.* (children of book). (#651)

This was caused when the CompiledPath#invertScannerFunctionRelationship was introduced, effectively in the graph of CompiledPath translating the $..book.length() to $.length($..book) but what it should have done (for length only) is translated it to $.length($..book.*).  Rather than make this bugfix in the CompiledPath source code I've made it to the Length source as this shouldn't be an issue for any other routine - length is specific in that its asking about the children (hence the .length() the dot there indicates next node(s)).

This addresses ticket 650 - https://github.com/json-path/JsonPath/issues/650 and adds unit tests for the same.

Co-authored-by: Matt Greenwood <61432137+mgreenwood1-chwy@users.noreply.github.com>
pull/697/head
Matt Greenwood 4 years ago committed by GitHub
parent
commit
812bea3f85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      json-path/src/main/java/com/jayway/jsonpath/internal/Path.java
  2. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
  3. 53
      json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java
  4. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java
  5. 7
      json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
  6. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  7. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java
  8. 20
      json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
  9. 24
      json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java

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

@ -60,5 +60,4 @@ public interface Path {
* @return true id this path is starts with '$' and false if the path starts with '@'
*/
boolean isRootPath();
}

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

@ -42,7 +42,7 @@ 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);

53
json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Length.java

@ -1,9 +1,16 @@
package com.jayway.jsonpath.internal.function.text;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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 +21,53 @@ import java.util.List;
*/
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<Parameter> 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);

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

@ -121,4 +121,8 @@ public class CompiledPath implements Path {
public String toString() {
return root.toString();
}
public RootPathToken getRoot() {
return root;
}
}

7
json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java

@ -84,4 +84,11 @@ public class FunctionPathToken extends PathToken {
public void setParameters(List<Parameter> parameters) {
this.functionParams = parameters;
}
public List<Parameter> getParameters() {
return this.functionParams;
}
public String getFunctionName() {
return this.functionName;
}
}

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

@ -218,4 +218,8 @@ public abstract class PathToken {
public void setNext(final PathToken next) {
this.next = next;
}
public PathToken getNext() {
return this.next;
}
}

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

@ -27,7 +27,7 @@ import static java.util.Arrays.asList;
*/
public class WildcardPathToken extends PathToken {
WildcardPathToken() {
public WildcardPathToken() {
}
@Override

20
json-path/src/test/java/com/jayway/jsonpath/BaseTest.java

@ -118,6 +118,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<Path, Object>());

24
json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java

@ -52,4 +52,28 @@ public class JsonOrgJsonProviderTest extends BaseTest {
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);
}
}

Loading…
Cancel
Save