Browse Source

Fixed merge conflict. Support for Functions in Predicates and Json Paths #103

pull/148/head
Kalle Stenflo 9 years ago
parent
commit
131f2a1a45
  1. 18
      README.md
  2. 33
      json-path/src/main/java/com/jayway/jsonpath/Function.java
  3. 3
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  4. 11
      json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java
  5. 6
      json-path/src/main/java/com/jayway/jsonpath/internal/Path.java
  6. 32
      json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java
  7. 71
      json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java
  8. 26
      json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java
  9. 18
      json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruFunction.java
  10. 55
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
  11. 26
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java
  12. 22
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java
  13. 22
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java
  14. 24
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java
  15. 20
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java
  16. 56
      json-path/src/main/java/com/jayway/jsonpath/internal/token/FunctionPathToken.java
  17. 13
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java
  18. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/token/PathTokenFactory.java
  19. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/token/RootPathToken.java
  20. 8
      json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
  21. 56
      json-path/src/test/java/com/jayway/jsonpath/Configurations.java
  22. 76
      json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java
  23. 75
      json-path/src/test/java/com/jayway/jsonpath/JsonProviderTestObjectMapping.java
  24. 45
      json-path/src/test/java/com/jayway/jsonpath/functions/BaseFunctionTest.java
  25. 105
      json-path/src/test/java/com/jayway/jsonpath/functions/JSONEntityFunctionTest.java
  26. 90
      json-path/src/test/java/com/jayway/jsonpath/functions/NumericFunctionTest.java

18
README.md

@ -61,6 +61,22 @@ Operators
| `[start:end]` | Array slice operator | | `[start:end]` | Array slice operator |
| `[?(<expression>)]` | Filter expression. Expression must evaluate to a boolean value. | | `[?(<expression>)]` | Filter expression. Expression must evaluate to a boolean value. |
Functions
---------
Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression.
The function output is dictated by the function itself.
| Function | Description | Output |
| :------------------------ | :----------------------------------------------------------------- |-----------|
| %min() | Provides the min value of an array of numbers | Double |
| %max() | Provides the max value of an array of numbers | Double |
| %avg() | Provides the average value of an array of numbers | Double |
| %stddev() | Provides the standard deviation value of an array of numbers | Double |
| %length() | Provides the length of an array | Integer |
Path Examples Path Examples
------------- -------------
@ -124,7 +140,7 @@ Given the json
| <a href="http://jsonpath.herokuapp.com/?path=$..book[?(@.price <= $['expensive'])]" target="_blank">$..book[?(@.price <= $['expensive'])]</a> | All books in store that are not "expensive" | | <a href="http://jsonpath.herokuapp.com/?path=$..book[?(@.price <= $['expensive'])]" target="_blank">$..book[?(@.price <= $['expensive'])]</a> | All books in store that are not "expensive" |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[?(@.author =~ /.*REES/i)]" target="_blank">$..book[?(@.author =~ /.*REES/i)]</a> | All books matching regex (ignore case) | | <a href="http://jsonpath.herokuapp.com/?path=$..book[?(@.author =~ /.*REES/i)]" target="_blank">$..book[?(@.author =~ /.*REES/i)]</a> | All books matching regex (ignore case) |
| <a href="http://jsonpath.herokuapp.com/?path=$..*" target="_blank">$..*</a> | Give me every thing | | <a href="http://jsonpath.herokuapp.com/?path=$..*" target="_blank">$..*</a> | Give me every thing |
| <a href="http://jsonpath.herokuapp.com/?path=$..book.%length()" target="_blank">$..book.%length()</a> | The number of books |
Reading a Document Reading a Document
------------------ ------------------

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

@ -0,0 +1,33 @@
package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef;
/**
* Defines the pattern by which a function can be executed over the result set in the particular path
* being grabbed. The Function's input is the content of the data from the json path selector and its output
* is defined via the functions behavior. Thus transformations in types can take place. Additionally, functions
* can accept multiple selectors in order to produce their output.
*
* Created by matt@mjgreenwood.net on 6/26/15.
*/
public interface Function {
/**
* Invoke the function and output a JSON object (or scalar) value which will be the result of executing the path
*
* @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
*
* @return
*/
Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx);
}

3
json-path/src/main/java/com/jayway/jsonpath/JsonPath.java

@ -172,6 +172,9 @@ public class JsonPath {
boolean optSuppressExceptions = configuration.containsOption(Option.SUPPRESS_EXCEPTIONS); boolean optSuppressExceptions = configuration.containsOption(Option.SUPPRESS_EXCEPTIONS);
try { try {
if(path.isFunctionPath()){
return path.evaluate(jsonObject, jsonObject, configuration).getValue(true);
}
if(optAsPathList){ if(optAsPathList){
return (T)path.evaluate(jsonObject, jsonObject, configuration).getPath(); return (T)path.evaluate(jsonObject, jsonObject, configuration).getPath();
} else { } else {

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

@ -16,7 +16,7 @@ package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.internal.token.EvaluationContextImpl; import com.jayway.jsonpath.internal.token.EvaluationContextImpl;
import com.jayway.jsonpath.internal.token.PathToken; import com.jayway.jsonpath.internal.token.RootPathToken;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -24,12 +24,12 @@ public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class); private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
private final PathToken root; private final RootPathToken root;
private final boolean isRootPath; private final boolean isRootPath;
public CompiledPath(PathToken root, boolean isRootPath) { public CompiledPath(RootPathToken root, boolean isRootPath) {
this.root = root; this.root = root;
this.isRootPath = isRootPath; this.isRootPath = isRootPath;
} }
@ -64,6 +64,11 @@ public class CompiledPath implements Path {
return root.isPathDefinite(); return root.isPathDefinite();
} }
@Override
public boolean isFunctionPath() {
return root.isFunctionPath();
}
@Override @Override
public String toString() { public String toString() {
return root.toString(); return root.toString();

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

@ -49,6 +49,12 @@ public interface Path {
*/ */
boolean isDefinite(); boolean isDefinite();
/**
*
* @return true id this path is a function
*/
boolean isFunctionPath();
/** /**
* *
* @return true id this path is starts with '$' and false if the path starts with '@' * @return true id this path is starts with '$' and false if the path starts with '@'

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

@ -5,6 +5,7 @@ import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.internal.token.ArrayIndexOperation; import com.jayway.jsonpath.internal.token.ArrayIndexOperation;
import com.jayway.jsonpath.internal.token.ArraySliceOperation; import com.jayway.jsonpath.internal.token.ArraySliceOperation;
import com.jayway.jsonpath.internal.token.FunctionPathToken;
import com.jayway.jsonpath.internal.token.PathTokenAppender; import com.jayway.jsonpath.internal.token.PathTokenAppender;
import com.jayway.jsonpath.internal.token.PathTokenFactory; import com.jayway.jsonpath.internal.token.PathTokenFactory;
import com.jayway.jsonpath.internal.token.RootPathToken; import com.jayway.jsonpath.internal.token.RootPathToken;
@ -35,6 +36,7 @@ public class PathCompiler {
private static final char MINUS = '-'; private static final char MINUS = '-';
private static final char ESCAPE = '\\'; private static final char ESCAPE = '\\';
private static final char TICK = '\''; private static final char TICK = '\'';
private static final char FUNCTION = '%';
private static final Cache cache = new Cache(200); private static final Cache cache = new Cache(200);
@ -127,12 +129,42 @@ public class PathCompiler {
case WILDCARD: case WILDCARD:
return readWildCardToken(appender) || return readWildCardToken(appender) ||
fail("Could not parse token at position " + path.position); fail("Could not parse token at position " + path.position);
case FUNCTION:
return readFunctionToken(appender) ||
fail("Could not parse token at position " + path.position);
default: default:
return readPropertyToken(appender) || return readPropertyToken(appender) ||
fail("Could not parse token at position " + path.position); fail("Could not parse token at position " + path.position);
} }
} }
//
// $function()
//
private boolean readFunctionToken(PathTokenAppender appender) {
if (path.currentCharIs(OPEN_SQUARE_BRACKET) || path.currentCharIs(WILDCARD) || path.currentCharIs(PERIOD) || path.currentCharIs(SPACE)) {
return false;
}
int startPosition = path.position;
int readPosition = startPosition;
int endPosition = 0;
while (path.inBounds(readPosition)) {
char c = path.charAt(readPosition);
if (c == OPEN_BRACKET && path.nextSignificantCharIs(readPosition, CLOSE_BRACKET)) {
endPosition = path.indexOfNextSignificantChar(readPosition, CLOSE_BRACKET);
break;
}
readPosition++;
}
path.setPosition(endPosition);
String function = path.subSequence(startPosition, endPosition + 1).toString();
appender.appendPathToken(PathTokenFactory.createFunctionPathToken(function));
return path.currentIsTail();
}
// //
// . // .
// //

71
json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java

@ -0,0 +1,71 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.internal.function.numeric.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Implements a factory that given a name of the function will return the Function implementation, or null
* if the value is not obtained.
*
* 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 FunctionFactory {
public static final Map<String, Class> FUNCTIONS;
static {
// New functions should be added here and ensure the name is not overridden
Map<String, Class> map = new HashMap<String, Class>();
// Math Functions
map.put("avg", Average.class);
map.put("stddev", StandardDeviation.class);
map.put("sum", Sum.class);
map.put("min", Min.class);
map.put("max", Max.class);
// JSON Entity Functions
map.put("length", Length.class);
FUNCTIONS = Collections.unmodifiableMap(map);
}
/**
* Either provides a pass thru function when the function cannot be properly mapped or otherwise returns the function
* implementation based on the name using the internal FUNCTION map
*
* @see #FUNCTIONS
* @see Function
*
* @param name
* The name of the function
*
* @return
* The implementation of a function
*
* @throws InvalidPathException
*/
public static Function newFunction(String name) throws InvalidPathException {
Function result = new PassthruFunction();
if (null != name && FUNCTIONS.containsKey(name) && Function.class.isAssignableFrom(FUNCTIONS.get(name))) {
try {
result = (Function)FUNCTIONS.get(name).newInstance();
} catch (InstantiationException e) {
throw new InvalidPathException("Function of name: " + name + " cannot be created", e);
} catch (IllegalAccessException e) {
throw new InvalidPathException("Function of name: " + name + " cannot be created", e);
}
}
return result;
}
}

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

@ -0,0 +1,26 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef;
import net.minidev.json.JSONArray;
import java.util.*;
/**
* Provides the length of a JSONArray Object
*
* Created by mattg on 6/26/15.
*/
public class Length implements Function {
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) {
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);
}
return null;
}
}

18
json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruFunction.java

@ -0,0 +1,18 @@
package com.jayway.jsonpath.internal.function;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef;
/**
* Defines the default behavior which is to return the model that is provided as input as output
*
* Created by mattg on 6/26/15.
*/
public class PassthruFunction implements Function {
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) {
return model;
}
}

55
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java

@ -0,0 +1,55 @@
package com.jayway.jsonpath.internal.function.numeric;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef;
import net.minidev.json.JSONArray;
import java.util.Iterator;
/**
* Defines the pattern for processing numerical values via an abstract implementation that iterates over the collection
* of JSONArray entities and verifies that each is a numerical value and then passes that along the abstract methods
*
*
* Created by mattg on 6/26/15.
*/
public abstract class AbstractAggregation implements Function {
/**
* Defines the next value in the array to the mathmatical function
*
* @param value
* The numerical value to process next
*/
protected abstract void next(Number value);
/**
* Obtains the value generated via the series of next value calls
*
* @return
* A numerical answer based on the input value provided
*/
protected abstract Number getValue();
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) {
if(ctx.configuration().jsonProvider().isArray(model)){
Iterable<?> objects = ctx.configuration().jsonProvider().toIterable(model);
for (Object obj : objects) {
// Object unwraped = ctx.configuration().jsonProvider().unwrap(obj);
// if (unwraped instanceof Number) {
// Number value = (Number) unwraped;
// next(value);
// }
if (obj instanceof Number) {
Number value = (Number) obj;
next(value);
}
}
return getValue();
}
return null;
}
}

26
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Average.java

@ -0,0 +1,26 @@
package com.jayway.jsonpath.internal.function.numeric;
/**
* Provides the average of a series of numbers in a JSONArray
*
* Created by mattg on 6/26/15.
*/
public class Average extends AbstractAggregation {
private Double summation = 0d;
private Double count = 0d;
@Override
protected void next(Number value) {
count++;
summation += value.doubleValue();
}
@Override
protected Number getValue() {
if (count != 0d) {
return summation / count;
}
return 0d;
}
}

22
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Max.java

@ -0,0 +1,22 @@
package com.jayway.jsonpath.internal.function.numeric;
/**
* Defines the summation of a series of JSONArray numerical values
*
* Created by mattg on 6/26/15.
*/
public class Max extends AbstractAggregation {
private Double max = Double.MIN_VALUE;
@Override
protected void next(Number value) {
if (max < value.doubleValue()) {
max = value.doubleValue();
}
}
@Override
protected Number getValue() {
return max;
}
}

22
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Min.java

@ -0,0 +1,22 @@
package com.jayway.jsonpath.internal.function.numeric;
/**
* Defines the summation of a series of JSONArray numerical values
*
* Created by mattg on 6/26/15.
*/
public class Min extends AbstractAggregation {
private Double min = Double.MAX_VALUE;
@Override
protected void next(Number value) {
if (min > value.doubleValue()) {
min = value.doubleValue();
}
}
@Override
protected Number getValue() {
return min;
}
}

24
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java

@ -0,0 +1,24 @@
package com.jayway.jsonpath.internal.function.numeric;
/**
* Provides the standard deviation of a series of numbers
*
* Created by mattg on 6/27/15.
*/
public class StandardDeviation extends AbstractAggregation {
private Double sumSq = 0d;
private Double sum = 0d;
private Double count = 0d;
@Override
protected void next(Number value) {
sum += value.doubleValue();
sumSq += value.doubleValue() * value.doubleValue();
count++;
}
@Override
protected Number getValue() {
return Math.sqrt(sumSq/count - sum*sum/count/count);
}
}

20
json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/Sum.java

@ -0,0 +1,20 @@
package com.jayway.jsonpath.internal.function.numeric;
/**
* Defines the summation of a series of JSONArray numerical values
*
* Created by mattg on 6/26/15.
*/
public class Sum extends AbstractAggregation {
private Double summation = 0d;
@Override
protected void next(Number value) {
summation += value.doubleValue();
}
@Override
protected Number getValue() {
return summation;
}
}

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

@ -0,0 +1,56 @@
package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.function.FunctionFactory;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Token representing a Function call to one of the functions produced via the FunctionFactory
*
* @see FunctionFactory
*
* Created by mattg on 6/27/15.
*/
public class FunctionPathToken extends PathToken {
private final String functionName;
private final String pathFragment;
public FunctionPathToken(String pathFragment) {
this.pathFragment = pathFragment;
Matcher matcher = Pattern.compile(".*?\\%(\\w+)\\(.*?").matcher(pathFragment);
if (matcher.matches()) {
functionName = matcher.group(1);
}
else {
// We'll end up throwing an error from the factory when we get that far
functionName = null;
}
}
@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
Function function = FunctionFactory.newFunction(functionName);
Object result = function.invoke(currentPath, parent, model, ctx);
ctx.addResult(currentPath, parent, result);
}
/**
* 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
*/
@Override
public boolean isTokenDefinite() {
return true;
}
@Override
public String getPathFragment() {
return pathFragment;
}
}

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

@ -14,11 +14,17 @@
*/ */
package com.jayway.jsonpath.internal.token; package com.jayway.jsonpath.internal.token;
import com.jayway.jsonpath.Function;
import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.Option; import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.PathRef;
import com.jayway.jsonpath.internal.Utils; import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.function.FunctionFactory;
import com.jayway.jsonpath.internal.function.numeric.Average;
import com.jayway.jsonpath.internal.function.Length;
import com.jayway.jsonpath.internal.function.PassthruFunction;
import com.jayway.jsonpath.internal.function.numeric.Sum;
import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.List; import java.util.List;
@ -67,7 +73,8 @@ public abstract class PathToken {
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP; PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP;
if (isLeaf()) { if (isLeaf()) {
ctx.addResult(evalPath, pathRef, propertyVal); ctx.addResult(evalPath, pathRef, propertyVal);
} else { }
else {
next().evaluate(evalPath, pathRef, propertyVal, ctx); next().evaluate(evalPath, pathRef, propertyVal, ctx);
} }
} else { } else {
@ -201,6 +208,10 @@ public abstract class PathToken {
return super.equals(obj); return super.equals(obj);
} }
public void invoke(Function function, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
ctx.addResult(currentPath, parent, function.invoke(currentPath, parent, model, ctx));
}
public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx);
public abstract boolean isTokenDefinite(); public abstract boolean isTokenDefinite();

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

@ -44,4 +44,8 @@ public class PathTokenFactory {
public static PathToken createPredicatePathToken(Predicate predicate) { public static PathToken createPredicatePathToken(Predicate predicate) {
return new PredicatePathToken(predicate); return new PredicatePathToken(predicate);
} }
public static PathToken createFunctionPathToken(String function) {
return new FunctionPathToken((function));
}
} }

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

@ -73,5 +73,7 @@ public class RootPathToken extends PathToken {
return true; return true;
} }
public boolean isFunctionPath() {
return (tail instanceof FunctionPathToken);
}
} }

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

@ -4,9 +4,11 @@ import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.spi.json.GsonJsonProvider; import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider; import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.internal.token.PredicateContextImpl; import com.jayway.jsonpath.internal.token.PredicateContextImpl;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
import java.util.HashMap; import java.util.HashMap;
@ -30,7 +32,11 @@ public class BaseTest {
.jsonProvider(new JacksonJsonNodeJsonProvider()) .jsonProvider(new JacksonJsonNodeJsonProvider())
.build(); .build();
public static final Configuration JSON_SMART_CONFIGURATION = Configuration.defaultConfiguration(); public static final Configuration JSON_SMART_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JsonSmartMappingProvider())
.jsonProvider(new JsonSmartJsonProvider())
.build();
public static final String JSON_BOOK_DOCUMENT = public static final String JSON_BOOK_DOCUMENT =
"{ " + "{ " +

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

@ -0,0 +1,56 @@
package com.jayway.jsonpath;
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.JsonSmartJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
import java.util.Arrays;
public class Configurations {
public static final Configuration GSON_CONFIGURATION = Configuration
.builder()
.mappingProvider(new GsonMappingProvider())
.jsonProvider(new GsonJsonProvider())
.build();
public static final Configuration JACKSON_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JacksonMappingProvider())
.jsonProvider(new JacksonJsonProvider())
.build();
public static final Configuration JACKSON_JSON_NODE_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JacksonMappingProvider())
.jsonProvider(new JacksonJsonNodeJsonProvider())
.build();
public static final Configuration JSON_SMART_CONFIGURATION = Configuration
.builder()
.mappingProvider(new JsonSmartMappingProvider())
.jsonProvider(new JsonSmartJsonProvider())
.build();
public static Iterable<Configuration> configurations() {
return Arrays.asList(
JSON_SMART_CONFIGURATION
,GSON_CONFIGURATION
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
);
}
public static Iterable<Configuration> objectMappingConfigurations() {
return Arrays.asList(
GSON_CONFIGURATION
,JACKSON_CONFIGURATION
,JACKSON_JSON_NODE_CONFIGURATION
);
}
}

76
json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java

@ -1,6 +1,8 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -8,84 +10,38 @@ import java.util.List;
import static com.jayway.jsonpath.JsonPath.using; import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@RunWith(Parameterized.class)
public class JsonProviderTest extends BaseTest { public class JsonProviderTest extends BaseTest {
private static final String JSON = private final Configuration conf;
"[" +
"{\n" +
" \"foo\" : \"foo0\",\n" +
" \"bar\" : 0,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp0\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo1\",\n" +
" \"bar\" : 1,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp1\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo2\",\n" +
" \"bar\" : 2,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp2\"}" +
"}" +
"]";
@Test public JsonProviderTest(Configuration conf) {
public void strings_are_unwrapped() { this.conf = conf;
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
} }
@Test @Parameterized.Parameters
public void integers_are_unwrapped() { public static Iterable<Configuration> configurations() {
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE); return Configurations.configurations();
assertThat(using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
} }
@Test @Test
public void ints_are_unwrapped() { public void strings_are_unwrapped() {
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE); assertThat(using(conf).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
assertThat(using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
} }
@Test @Test
public void list_of_numbers() { public void integers_are_unwrapped() {
assertThat(using(conf).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
TypeRef<List<Double>> typeRef = new TypeRef<List<Double>>() {};
assertThat(using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
} }
@Test @Test
public void test_type_ref() throws IOException { public void ints_are_unwrapped() {
TypeRef<List<FooBarBaz<Sub>>> typeRef = new TypeRef<List<FooBarBaz<Sub>>>() {}; assertThat(using(conf).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
assertThat(using(JACKSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
assertThat(using(JACKSON_JSON_NODE_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
assertThat(using(GSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
} }
public static class FooBarBaz<T> {
public T gen;
public String foo;
public Long bar;
public boolean baz;
}
public static class Sub {
public String prop;
}
} }

75
json-path/src/test/java/com/jayway/jsonpath/JsonProviderTestObjectMapping.java

@ -0,0 +1,75 @@
package com.jayway.jsonpath;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.util.List;
import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(Parameterized.class)
public class JsonProviderTestObjectMapping extends BaseTest {
private static final String JSON =
"[" +
"{\n" +
" \"foo\" : \"foo0\",\n" +
" \"bar\" : 0,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp0\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo1\",\n" +
" \"bar\" : 1,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp1\"}" +
"}," +
"{\n" +
" \"foo\" : \"foo2\",\n" +
" \"bar\" : 2,\n" +
" \"baz\" : true,\n" +
" \"gen\" : {\"prop\" : \"yepp2\"}" +
"}" +
"]";
private final Configuration conf;
public JsonProviderTestObjectMapping(Configuration conf) {
this.conf = conf;
}
@Parameterized.Parameters
public static Iterable<Configuration> configurations() {
return Configurations.objectMappingConfigurations();
}
@Test
public void list_of_numbers() {
TypeRef<List<Double>> typeRef = new TypeRef<List<Double>>() {};
assertThat(using(conf).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
}
@Test
public void test_type_ref() throws IOException {
TypeRef<List<FooBarBaz<Sub>>> typeRef = new TypeRef<List<FooBarBaz<Sub>>>() {};
assertThat(using(conf).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
}
public static class FooBarBaz<T> {
public T gen;
public String foo;
public Long bar;
public boolean baz;
}
public static class Sub {
public String prop;
}
}

45
json-path/src/test/java/com/jayway/jsonpath/functions/BaseFunctionTest.java

@ -0,0 +1,45 @@
package com.jayway.jsonpath.functions;
import com.jayway.jsonpath.Configuration;
import java.io.IOException;
import java.util.Scanner;
import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Created by mattg on 6/27/15.
*/
public class BaseFunctionTest {
protected static final String NUMBER_SERIES = "{\"numbers\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}";
protected static final String TEXT_SERIES = "{\"text\" : [ \"a\", \"b\", \"c\", \"d\", \"e\", \"f\" ]}";
/**
* Verify the function returns the correct result based on the input expectedValue
*
* @param pathExpr
* The path expression to execute
*
* @param json
* The json document (actual content) to parse
*
* @param expectedValue
* The expected value to be returned from the test
*/
protected void verifyFunction(Configuration conf, String pathExpr, String json, Object expectedValue) {
Object result = using(conf).parse(json).read(pathExpr);
assertThat(result).isEqualTo(expectedValue);
}
protected void verifyMathFunction(Configuration conf, String pathExpr, Object expectedValue) {
verifyFunction(conf, pathExpr, NUMBER_SERIES, expectedValue);
}
protected String getResourceAsText(String resourceName) throws IOException {
return new Scanner(BaseFunctionTest.class.getResourceAsStream(resourceName), "UTF-8").useDelimiter("\\A").next();
}
}

105
json-path/src/test/java/com/jayway/jsonpath/functions/JSONEntityFunctionTest.java

@ -0,0 +1,105 @@
package com.jayway.jsonpath.functions;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import net.minidev.json.JSONArray;
import org.junit.Test;
/**
* Verifies methods that are helper implementations of functions for manipulating JSON entities, i.e.
* length, etc.
*
* Created by mattg on 6/27/15.
*/
public class JSONEntityFunctionTest extends BaseFunctionTest {
private static final String BATCH_JSON = "{\n" +
" \"batches\": {\n" +
" \"minBatchSize\": 10,\n" +
" \"results\": [\n" +
" {\n" +
" \"productId\": 23,\n" +
" \"values\": [\n" +
" 2,\n" +
" 45,\n" +
" 34,\n" +
" 23,\n" +
" 3,\n" +
" 5,\n" +
" 4,\n" +
" 3,\n" +
" 2,\n" +
" 1,\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"productId\": 23,\n" +
" \"values\": [\n" +
" 52,\n" +
" 3,\n" +
" 12,\n" +
" 11,\n" +
" 18,\n" +
" 22,\n" +
" 1\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";
private Configuration conf = Configurations.JSON_SMART_CONFIGURATION;
@Test
public void testLengthOfTextArray() {
// The length of JSONArray is an integer
System.out.println(TEXT_SERIES);
verifyFunction(conf, "$['text'].%length()", TEXT_SERIES, 6);
}
@Test
public void testLengthOfNumberArray() {
// The length of JSONArray is an integer
verifyFunction(conf, "$.numbers.%length()", NUMBER_SERIES, 10);
}
@Test
public void testLengthOfStructure() {
verifyFunction(conf, "$.batches.%length()", BATCH_JSON, 2);
}
/**
* The fictitious use-case/story - is we have a collection of batches with values indicating some quality metric.
* We want to determine the average of the values for only the batch's values where the number of items in the batch
* is greater than the min batch size which is encoded in the JSON document.
*
* We use the length function in the predicate to determine the number of values in each batch and then for those
* batches where the count is greater than min we calculate the average batch value.
*
* Its completely contrived example, however, this test exercises functions within predicates.
*/
@Test
public void testPredicateWithFunctionCallSingleMatch() {
String path = "$.batches.results[?(@.values.%length() >= $.batches.minBatchSize)].values.%avg()";
// Its an array because in some use-cases the min size might match more than one batch and thus we'll get
// the average out for each collection
JSONArray values = new JSONArray();
values.add(12.2d);
verifyFunction(conf, path, BATCH_JSON, values);
}
@Test
public void testPredicateWithFunctionCallTwoMatches() {
String path = "$.batches.results[?(@.values.%length() >= 3)].values.%avg()";
// Its an array because in some use-cases the min size might match more than one batch and thus we'll get
// the average out for each collection
JSONArray values = new JSONArray();
values.add(12.2d);
values.add(17d);
verifyFunction(conf, path, BATCH_JSON, values);
}
}

90
json-path/src/test/java/com/jayway/jsonpath/functions/NumericFunctionTest.java

@ -0,0 +1,90 @@
package com.jayway.jsonpath.functions;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import static com.jayway.jsonpath.Configurations.*;
import static com.jayway.jsonpath.JsonPath.using;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.runners.Parameterized.Parameters;
/**
* Defines functional tests around executing:
*
* - sum
* - avg
* - stddev
*
* for each of the above, executes the test and verifies that the results are as expected based on a static input
* and static output.
*
* Created by mattg on 6/26/15.
*/
@RunWith(Parameterized.class)
public class NumericFunctionTest extends BaseFunctionTest {
private static final Logger logger = LoggerFactory.getLogger(NumericFunctionTest.class);
private Configuration conf = Configurations.GSON_CONFIGURATION;
public NumericFunctionTest(Configuration conf) {
logger.debug("Testing with configuration {}", conf.getClass().getName());
this.conf = conf;
}
@Parameters
public static Iterable<Configuration> configurations() {
return Configurations.configurations();
}
@Test
public void testAverageOfDoubles() {
verifyMathFunction(conf, "$.numbers.%avg()", 5.5);
}
@Test
public void testSumOfDouble() {
verifyMathFunction(conf, "$.numbers.%sum()", (10d * (10d + 1d)) / 2d);
}
@Test
public void testMaxOfDouble() {
verifyMathFunction(conf, "$.numbers.%max()", 10d);
}
@Test
public void testMinOfDouble() {
verifyMathFunction(conf, "$.numbers.%min()", 1d);
}
@Test
public void testStdDevOfDouble() {
verifyMathFunction(conf, "$.numbers.%stddev()", 2.8722813232690143d);
}
/**
* Expect that for an invalid function name we'll get back the original input to the function
*/
// @Test
// @Ignore
// public void testInvalidFunctionNameNegative() {
// JSONArray numberSeries = new JSONArray();
// numberSeries.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// assertThat(using(conf).parse(NUMBER_SERIES).read("$.numbers.%foo()")).isEqualTo(numberSeries);
// }
}
Loading…
Cancel
Save