Skip to content
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 @@ -290,7 +290,10 @@ private List<Parameter> parseFunctionParameters(String funcName) {

// Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the
// last parameter boundary
int groupParen = 1, groupBracket = 0, groupBrace = 0, groupQuote = 0;
// quote tracks the active string delimiter (0 when not inside a quoted string) so single- and double-quoted
// literals can both be used as JSON parameters without treating the other quote as a terminator.
int groupParen = 1, groupBracket = 0, groupBrace = 0;
char quote = 0;
boolean endOfStream = false;
char priorChar = 0;
List<Parameter> parameters = new ArrayList<Parameter>();
Expand All @@ -305,7 +308,7 @@ private List<Parameter> parseFunctionParameters(String funcName) {
continue;
}

if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c || MINUS == c) {
if (c == OPEN_BRACE || isDigit(c) || DOUBLE_QUOTE == c || SINGLE_QUOTE == c || MINUS == c) {
type = ParamType.JSON;
}
else if (isPathContext(c)) {
Expand All @@ -315,11 +318,13 @@ else if (isPathContext(c)) {

switch (c) {
case DOUBLE_QUOTE:
if (priorChar != '\\' && groupQuote > 0) {
groupQuote--;
}
else {
groupQuote++;
case SINGLE_QUOTE:
if (priorChar != '\\') {
if (quote == 0) {
quote = c;
} else if (quote == c) {
quote = 0;
}
}
break;
case OPEN_PARENTHESIS:
Expand Down Expand Up @@ -356,16 +361,18 @@ else if (isPathContext(c)) {
case COMMA:
// In this state we've reach the end of a function parameter and we can pass along the parameter string
// to the parser
if ((0 == groupQuote && 0 == groupBrace && 0 == groupBracket
if ((0 == quote && 0 == groupBrace && 0 == groupBracket
&& ((0 == groupParen && CLOSE_PARENTHESIS == c) || 1 == groupParen))) {
endOfStream = (0 == groupParen);

if (null != type) {
Parameter param = null;
switch (type) {
case JSON:
// parse the json and set the value
param = new Parameter(parameter.toString());
// parse the json and set the value.
// Single-quoted string literals are accepted in path function args
// (same as filter predicates) but are not valid JSON; normalize them.
param = new Parameter(normalizeFunctionParameterJson(parameter.toString()));
break;
case PATH:
LinkedList<Predicate> predicates = new LinkedList<>();
Expand Down Expand Up @@ -398,6 +405,32 @@ private boolean isWhitespace(char c) {
return (c == SPACE || c == TAB || c == LF || c == CR);
}

/**
* Convert a path-language single-quoted string literal into a JSON double-quoted string so
* providers that require strict JSON (e.g. Jackson) can parse the function parameter.
* Non-string JSON tokens (numbers, objects, arrays, double-quoted strings) are left unchanged.
*/
private static String normalizeFunctionParameterJson(String json) {
if (json == null) {
return null;
}
int start = 0;
int end = json.length();
while (start < end && Character.isWhitespace(json.charAt(start))) {
start++;
}
while (end > start && Character.isWhitespace(json.charAt(end - 1))) {
end--;
}
if (end - start >= 2
&& json.charAt(start) == SINGLE_QUOTE
&& json.charAt(end - 1) == SINGLE_QUOTE) {
String unescaped = Utils.unescape(json.substring(start + 1, end - 1));
return DOUBLE_QUOTE + Utils.escape(unescaped, false) + DOUBLE_QUOTE;
}
return json;
}

//
// [?], [?,?, ..]
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ public void testStringConcatWithJSONParameter(Configuration conf) {
verifyTextFunction(conf, "$.text.concat(\"-\", \"ghijk\")", "abcdef-ghijk");
}

/**
* Single-quoted string literals in function args should match double-quoted behavior
* (same as filter predicates). Regression for https://github.com/json-path/JsonPath/issues/1078
*/
@ParameterizedTest
@MethodSource("configurations")
public void testStringConcatWithSingleQuotedJSONParameter(Configuration conf) {
verifyFunction(conf, "$.concat('hello')", "{}", "hello");
verifyTextFunction(conf, "$.text.concat('-', 'ghijk')", "abcdef-ghijk");
verifyFunction(conf, "$.concat($.x,'-',$.y)", "{\"x\":\"P\",\"y\":\"Q\"}", "P-Q");
// Apostrophe inside double quotes must not break argument grouping
verifyFunction(conf, "$.concat(\"it's\")", "{}", "it's");
}


@ParameterizedTest
@MethodSource("configurations")
Expand Down