diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java b/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java index 3d99b1b1..7decbd64 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java +++ b/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(); - } 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 d809628a..2fcd3a33 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 @@ -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); 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 e70f4e3f..785938c9 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,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 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/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java index 131e068f..80ac2bd4 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 @@ -121,4 +121,8 @@ public class CompiledPath implements Path { public String toString() { return root.toString(); } + + public RootPathToken getRoot() { + return root; + } } 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 84f2c481..bc05bc5f 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 @@ -84,4 +84,11 @@ public class FunctionPathToken extends PathToken { 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/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java index 5a5046d8..69e0b8ba 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 @@ -218,4 +218,8 @@ public abstract class PathToken { 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/WildcardPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/WildcardPathToken.java index 7f3ccec1..455cea1f 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 @@ -27,7 +27,7 @@ import static java.util.Arrays.asList; */ public class WildcardPathToken extends PathToken { - WildcardPathToken() { + public WildcardPathToken() { } @Override 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 714aa92b..a31394e6 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java +++ b/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()); 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 59ba665c..47ce0c1e 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java +++ b/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); + } + }