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 3a31151f..21980ad0 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 @@ -12,6 +12,7 @@ import com.jayway.jsonpath.internal.function.sequence.First; import com.jayway.jsonpath.internal.function.sequence.Index; import com.jayway.jsonpath.internal.function.sequence.Last; import com.jayway.jsonpath.internal.function.text.Concatenate; +import com.jayway.jsonpath.internal.function.text.Join; import com.jayway.jsonpath.internal.function.text.Length; import java.util.Collections; @@ -43,6 +44,7 @@ public class PathFunctionFactory { // Text Functions map.put("concat", Concatenate.class); + map.put("join", Join.class); // JSON Entity Functions map.put(Length.TOKEN_NAME, Length.class); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java new file mode 100644 index 00000000..19e7b5fc --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java @@ -0,0 +1,166 @@ +package com.jayway.jsonpath.internal.function.text; + +import com.jayway.jsonpath.internal.EvaluationContext; +import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.internal.function.ParamType; +import com.jayway.jsonpath.internal.function.Parameter; +import com.jayway.jsonpath.internal.function.PathFunction; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * join values in an array, supply two grammars: + *

+ * 1. [].join([delimiter],[..path]) + * 2. $.join([delimiter],[..path]) + */ +public class Join implements PathFunction { + @Override + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { + String delimiter = this.getDelimiterFromParameters(parameters); + Collection results = Collections.emptyList(); + + // [].join([..path]) + if (ctx.configuration().jsonProvider().isArray(model)) { + results = this.joinByArrayModel(model, ctx, parameters); + } + + // $.join([delimiter],[..path]) + if (null == results || results.size() == 0) { + results = this.joinByParams(ctx, parameters); + } + + if (null == results || results.size() == 0) { + return ""; + } + return String.join(delimiter, results); + } + + + /** + * get results by model + * + * @param model + * @param ctx + * @param parameters + * @return + */ + protected Collection joinByArrayModel(Object model, EvaluationContext ctx, List parameters) { + Collection resultList = new ArrayList<>(); + List pathParams = Optional.ofNullable(parameters).orElseGet(Collections::emptyList) + .stream().filter(item -> ParamType.PATH.equals(item.getType())).collect(Collectors.toList()); + + if (pathParams.size() == 0) { + return this.arrayIterableToList(ctx, model, item -> null); + } + + for (Parameter pathParam : pathParams) { + List list = this.arrayIterableToList(ctx, model, item -> { + Object value = pathParam.getPath().evaluate(item, model, ctx.configuration()).getValue(); + return this.arrayIterableToList(ctx, value, obj -> null); + }); + if (null == list || list.size() == 0) { + continue; + } + resultList.addAll(list); + } + return resultList; + } + + + /** + * get results by params + * + * @param ctx + * @param parameters + * @return + */ + protected Collection joinByParams(EvaluationContext ctx, List parameters) { + if (null == parameters || parameters.size() == 0) { + return null; + } + List notJsonParams = parameters.stream().filter(item -> ParamType.PATH.equals(item.getType())).collect(Collectors.toList()); + if (notJsonParams.size() == 0) { + return parameters.stream().map(Parameter::getValue).filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList()); + } + List list = Parameter.toList(String.class, ctx, notJsonParams); + return list.size() > 0 ? list : null; + } + + /** + * get delimiter + * + * @param parameters + * @return + */ + protected String getDelimiterFromParameters(List parameters) { + if (null != parameters && parameters.size() >= 1) { + Parameter parameter = parameters.get(0); + if (ParamType.JSON.equals(parameter.getType())) { + return parameter.getValue().toString(); + } + } + return ","; + } + + + /** + * literal quantity type + * + * @param obj + * @return + */ + protected boolean simpleType(Object obj) { + if (null == obj) { + return false; + } + return obj instanceof Number || obj instanceof CharSequence || + obj instanceof Boolean || obj instanceof Character; + } + + + /** + * iterable + * + * @param ctx + * @param model + * @param itemIsNotSimpleFunc + */ + protected List arrayIterableToList(EvaluationContext ctx, Object model, Function> itemIsNotSimpleFunc) { + if (null == model) { + return Collections.emptyList(); + } + + if (this.simpleType(model)) { + return Collections.singletonList(model.toString()); + } + + boolean isArray = ctx.configuration().jsonProvider().isArray(model); + if (!isArray) { + return Collections.emptyList(); + } + + List resultList = new ArrayList<>(); + Iterable iterable = ctx.configuration().jsonProvider().toIterable(model); + for (Object obj : iterable) { + if (null == obj) { + continue; + } + + boolean isSimpleType = this.simpleType(obj); + if (isSimpleType) { + resultList.add(obj.toString()); + continue; + } + + List items = itemIsNotSimpleFunc.apply(obj); + if (null == items || items.size() == 0) { + continue; + } + resultList.addAll(items); + } + return resultList; + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java index 3017cbb6..cf6d0530 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/JSONEntityPathFunctionTest.java @@ -2,6 +2,7 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configurations; +import com.jayway.jsonpath.Option; import net.minidev.json.JSONArray; import org.junit.Test; @@ -106,4 +107,16 @@ public class JSONEntityPathFunctionTest extends BaseFunctionTest { verifyFunction(conf, path, BATCH_JSON, values); } + + + @Test + public void testParameterJoinFunctionCall() { + Configuration configuration = Configuration.builder().options(Option.DEFAULT_PATH_LEAF_TO_NULL, Option.SUPPRESS_EXCEPTIONS).build(); + verifyFunction(configuration, "$.text.join()", TEXT_SERIES, "a,b,c,d,e,f"); + verifyFunction(configuration, "$.text.join(\"|\")", TEXT_SERIES, "a|b|c|d|e|f"); + verifyFunction(configuration, "$.join($.batches.results[*].productId)", BATCH_JSON, "23,23"); + verifyFunction(configuration, "$.join(\" _ \",$.batches.results[*].values[?(@ > 10)])", BATCH_JSON, "45 _ 34 _ 23 _ 52 _ 12 _ 11 _ 18 _ 22"); + verifyFunction(configuration, "$.join(\" \",$.batches.results[*].values[?(@ < 10)], $.batches.results[*].productId)", BATCH_JSON, "2 3 5 4 3 2 1 3 1 23 23"); + verifyFunction(configuration, "$.batches.results.join(\" \", $.productId, $.values[?(@ < 10)])", BATCH_JSON, "23 23 2 3 5 4 3 2 1 3 1"); + } }