Kalle Stenflo
9 years ago
26 changed files with 852 additions and 67 deletions
@ -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); |
||||||
|
} |
@ -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; |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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…
Reference in new issue