Adding support for custom path functions#286
Adding support for custom path functions#286jakeswenson wants to merge 1 commit intojson-path:masterfrom
Conversation
Adding a configuration value for a path function factory
| public MapJsonPathFunctionFactory( | ||
| final Map<String, Class<? extends PathFunction>> pathFunctionMap) { | ||
|
|
||
| this.pathFunctionMap = pathFunctionMap; |
There was a problem hiding this comment.
I suggest you clone the list here so that modifications to the original map don't affect the factory.
| public PathFunction newFunction(final String name) throws InvalidPathException { | ||
| Class functionClazz = pathFunctionMap.get(name); | ||
| if(functionClazz == null){ | ||
| throw new InvalidPathException("Function with name: " + name + " does not exists."); |
There was a problem hiding this comment.
Should be "does not exist."
| * | ||
| * @throws InvalidPathException | ||
| */ | ||
| @Deprecated |
There was a problem hiding this comment.
Maybe we should deprecate the whole class
| public class PathFunctionFactory { | ||
|
|
||
| public static final Map<String, Class> FUNCTIONS; | ||
| public static final Map<String, Class<? extends PathFunction>> FUNCTIONS; |
There was a problem hiding this comment.
Maybe move the value/initialization to the Configuration class and just keep a reference here?
|
We need to come up with a SPI to prevent use of internal classes. 3.0 feature? |
|
LGTM, but I'd love to see some tests and maybe an addition to the README file with a code snippet that registers a new function? |
| @@ -0,0 +1,24 @@ | |||
| package com.jayway.jsonpath.internal.function; | |||
There was a problem hiding this comment.
This should probably be moved to a package within com.jayway.jsonpath.spi, along with the implementation class.
|
I see no harm in adding this in a minor release if it doesn't break any existing API, but maybe we should give it some more thought and not add it to 2.3.0 (which I hope will be ready pretty soon :-) ). |
| } | ||
|
|
||
| @Override | ||
| public PathFunction newFunction(final String name) throws InvalidPathException { |
There was a problem hiding this comment.
I question whether PathFunction as an interface is an appropriate expression for a user facing function feature - there's some nuance to know in what context the function is being invoked that is internal state of the JsonPath library. Breaking apart the types of PathFunctions (aggregations vs straight calculations) might be a good way to tease apart the interface into something more meaningful for the end-user.
Also, does the function need to take parameters -- can a sub-select be a parameter to the function - expressing that in a more general way such that the consumer doesn't need to grok the internals of JsonPath would be ideal.
Certainly keep this method as all interfaces/implementations are a PathFunction, but I think you might want to introduce additional factory methods for aggregation vs straight calculation and then abstract away the nuance of accepting parameters.
|
so this PR break down halfway ? |
|
I use reflection to force add custom path functions. The implementation is not elegant, but it works. Here is an example below: pom.xml <dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.8.0</version>
</dependency>code: import com.google.common.collect.ImmutableSet;
import com.jayway.jsonpath.*;
import com.jayway.jsonpath.internal.EvaluationContext;
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.function.PathFunctionFactory;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JsonPathConfig {
static {
initJsonPathConfig();
addCustomFunctions();
}
private static void addCustomFunctions() {
try {
// force access unmodifiableMap!!
@SuppressWarnings({"unchecked", "rawtypes"})
Map<String, Class> m = (Map<String, Class>) FieldUtils.readField(PathFunctionFactory.FUNCTIONS, "m", true);
m.put("copyN", CopyNFunction.class);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static void initJsonPathConfig() {
// https://github.com/json-path/JsonPath#jsonprovider-spi
Configuration.setDefaults(new Configuration.Defaults() {
private final JsonProvider jsonProvider = new JacksonJsonProvider();
private final MappingProvider mappingProvider = new JacksonMappingProvider();
@Override
public JsonProvider jsonProvider() {
return jsonProvider;
}
@Override
public MappingProvider mappingProvider() {
return mappingProvider;
}
@Override
public Set<Option> options() {
return ImmutableSet.of(Option.DEFAULT_PATH_LEAF_TO_NULL);
}
});
}
public static class CopyNFunction implements PathFunction {
@Override
public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List<Parameter> parameters) {
if (parameters == null || parameters.size() != 1) {
throw new InvalidPathException("copyN function accept 1 parameter, current: " + CollectionUtils.size(parameters));
}
Object parameter = parameters.get(0).getValue();
if (!(parameter instanceof Number)) {
throw new InvalidPathException("copyN function parameter type should be Number, current: " + parameter.getClass());
}
int size = ((Number) parameter).intValue();
List<Object> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
list.add(model);
}
return list;
}
}
public static void main(String[] args) {
String content = "{ \"foo\": \"bar\" }";
DocumentContext context = JsonPath.parse(content);
Object obj = context.read("$.foo.copyN(3)");
System.out.println(obj);
}
}the JsonPath |
Adding a configuration value for a path function factory