Skip to content

feat: add join func #976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
* <p>
* 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<Parameter> parameters) {
String delimiter = this.getDelimiterFromParameters(parameters);
Collection<String> 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<String> joinByArrayModel(Object model, EvaluationContext ctx, List<Parameter> parameters) {
Collection<String> resultList = new ArrayList<>();
List<Parameter> 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<String> 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<String> joinByParams(EvaluationContext ctx, List<Parameter> parameters) {
if (null == parameters || parameters.size() == 0) {
return null;
}
List<Parameter> 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<String> list = Parameter.toList(String.class, ctx, notJsonParams);
return list.size() > 0 ? list : null;
}

/**
* get delimiter
*
* @param parameters
* @return
*/
protected String getDelimiterFromParameters(List<Parameter> 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<String> arrayIterableToList(EvaluationContext ctx, Object model, Function<Object, List<String>> 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<String> 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<String> items = itemIsNotSimpleFunc.apply(obj);
if (null == items || items.size() == 0) {
continue;
}
resultList.addAll(items);
}
return resultList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import com.jayway.jsonpath.Option;
import net.minidev.json.JSONArray;
import org.junit.Test;

Expand Down Expand Up @@ -106,4 +107,16 @@ public void testPredicateWithFunctionCallTwoMatches() {
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");
}
}