Skip to content

Commit 5a05075

Browse files
wenshaoclaude
andauthored
fix: JSONPath syntax error when 'in'/'rlike' filter follows other filters (#3997) (#4030)
* fix: JSONPath syntax error when 'in' filter follows other filters (#3997) The IN/NOT_IN, RLIKE, and CONTAINS cases in parseFilter() unconditionally consumed a closing ')' even when called from parseFilterRest() where parentheses=false, stealing the outer filter's closing paren. Guard closing paren consumption with 'if (parentheses)' to match the BETWEEN case and the default path at the end of parseFilter(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use 'parentheses || filterNests > 0' instead of just 'parentheses' The previous fix used only 'if (parentheses)' to guard closing paren consumption in RLIKE/IN/CONTAINS cases, but this broke expressions using filterNests-tracked grouping parens like: $[?( (@.name =~ /aa/ && (@.city=='aa')) && @.age==18 )] The correct condition is 'parentheses || filterNests > 0' which handles both parentheses-tracked and filterNests-tracked contexts. Fixes regression in Issue1516.testFastjson2JSONPathCompile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add @tag("regression") and rlike chaining tests Address review feedback: - Add @tag("regression") to match other tests in issues_3900 - Add tests for rlike after equality with && and || Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 14ea6ea commit 5a05075

2 files changed

Lines changed: 113 additions & 6 deletions

File tree

core/src/main/java/com/alibaba/fastjson2/JSONPathParser.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -816,8 +816,10 @@ JSONPathSegment parseFilter() {
816816
filterNests--;
817817
segment = parseFilterRest(segment);
818818
}
819-
if (!jsonReader.nextIfMatch(')')) {
820-
throw new JSONException(jsonReader.info("jsonpath syntax error"));
819+
if (parentheses || filterNests > 0) {
820+
if (!jsonReader.nextIfMatch(')')) {
821+
throw new JSONException(jsonReader.info("jsonpath syntax error"));
822+
}
821823
}
822824
return segment;
823825
}
@@ -863,8 +865,10 @@ JSONPathSegment parseFilter() {
863865
filterNests--;
864866
segment = parseFilterRest(segment);
865867
}
866-
if (!jsonReader.nextIfMatch(')')) {
867-
throw new JSONException(jsonReader.info("jsonpath syntax error"));
868+
if (parentheses || filterNests > 0) {
869+
if (!jsonReader.nextIfMatch(')')) {
870+
throw new JSONException(jsonReader.info("jsonpath syntax error"));
871+
}
868872
}
869873

870874
return segment;
@@ -915,8 +919,14 @@ JSONPathSegment parseFilter() {
915919
if (!jsonReader.nextIfMatch(')')) {
916920
throw new JSONException(jsonReader.info("jsonpath syntax error"));
917921
}
918-
if (!jsonReader.nextIfMatch(')')) {
919-
throw new JSONException(jsonReader.info("jsonpath syntax error"));
922+
if (jsonReader.ch == '&' || jsonReader.ch == '|' || jsonReader.ch == 'a' || jsonReader.ch == 'o') {
923+
filterNests--;
924+
segment = parseFilterRest(segment);
925+
}
926+
if (parentheses || filterNests > 0) {
927+
if (!jsonReader.nextIfMatch(')')) {
928+
throw new JSONException(jsonReader.info("jsonpath syntax error"));
929+
}
920930
}
921931

922932
return segment;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.alibaba.fastjson2.issues_3900;
2+
3+
import com.alibaba.fastjson2.JSON;
4+
import com.alibaba.fastjson2.JSONArray;
5+
import com.alibaba.fastjson2.JSONPath;
6+
import com.alibaba.fastjson2.JSONReader;
7+
import org.junit.jupiter.api.Tag;
8+
import org.junit.jupiter.api.Test;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
13+
@Tag("regression")
14+
public class Issue3997 {
15+
static final String JSON_STR = "{\n"
16+
+ " \"riskRequests\": [\n"
17+
+ " {\"requestRiskType\": \"05\", \"customerType\": \"01\", \"customerName\": \"Alice\"},\n"
18+
+ " {\"requestRiskType\": \"05\", \"customerType\": \"02\", \"customerName\": \"Bob\"},\n"
19+
+ " {\"requestRiskType\": \"06\", \"customerType\": \"04\", \"customerName\": \"Charlie\"}\n"
20+
+ " ]\n"
21+
+ "}";
22+
23+
@Test
24+
public void testInFilterAfterEquality() {
25+
Object result = JSONPath.eval(JSON_STR,
26+
"$.riskRequests[?(@.requestRiskType=='05' && @.customerType in ('01','04'))].customerName");
27+
assertNotNull(result);
28+
JSONArray arr = (JSONArray) result;
29+
assertEquals(1, arr.size());
30+
assertEquals("Alice", arr.get(0));
31+
}
32+
33+
@Test
34+
public void testInFilterBeforeEquality() {
35+
Object result = JSONPath.eval(JSON_STR,
36+
"$.riskRequests[?(@.customerType in ('01','04') && @.requestRiskType=='05')].customerName");
37+
assertNotNull(result);
38+
JSONArray arr = (JSONArray) result;
39+
assertEquals(1, arr.size());
40+
assertEquals("Alice", arr.get(0));
41+
}
42+
43+
@Test
44+
public void testInFilterAlone() {
45+
Object result = JSONPath.eval(JSON_STR,
46+
"$.riskRequests[?(@.customerType in ('01','04'))].customerName");
47+
assertNotNull(result);
48+
JSONArray arr = (JSONArray) result;
49+
assertEquals(2, arr.size());
50+
}
51+
52+
@Test
53+
public void testInFilterWithOr() {
54+
Object result = JSONPath.eval(JSON_STR,
55+
"$.riskRequests[?(@.requestRiskType=='06' || @.customerType in ('01'))].customerName");
56+
assertNotNull(result);
57+
JSONArray arr = (JSONArray) result;
58+
assertEquals(2, arr.size());
59+
}
60+
61+
@Test
62+
public void testNotInAfterEquality() {
63+
Object result = JSONPath.eval(JSON_STR,
64+
"$.riskRequests[?(@.requestRiskType=='05' && @.customerType not in ('01'))].customerName");
65+
assertNotNull(result);
66+
JSONArray arr = (JSONArray) result;
67+
assertEquals(1, arr.size());
68+
assertEquals("Bob", arr.get(0));
69+
}
70+
71+
@Test
72+
public void testNumericInAfterEquality() {
73+
String json = "{\"items\":[{\"type\":\"a\",\"code\":1},{\"type\":\"a\",\"code\":2},{\"type\":\"b\",\"code\":1}]}";
74+
Object result = JSONPath.eval(json,
75+
"$.items[?(@.type=='a' && @.code in (1))].code");
76+
assertNotNull(result);
77+
JSONArray arr = (JSONArray) result;
78+
assertEquals(1, arr.size());
79+
assertEquals(1, arr.get(0));
80+
}
81+
82+
@Test
83+
public void testRlikeAfterEquality() {
84+
String jsonArray = "[{\"name\":\"abc\",\"age\":18},{\"name\":\"def\",\"age\":18},{\"name\":\"xyz\",\"age\":20}]";
85+
String path = "$[?(@.age==18 && @.name =~ /abc/)]";
86+
Object result = JSONPath.of(path).extract(JSONReader.of(jsonArray));
87+
assertEquals("[{\"name\":\"abc\",\"age\":18}]", JSON.toJSONString(result));
88+
}
89+
90+
@Test
91+
public void testRlikeAfterEqualityWithOr() {
92+
String jsonArray = "[{\"name\":\"abc\",\"age\":18},{\"name\":\"def\",\"age\":20},{\"name\":\"xyz\",\"age\":20}]";
93+
String path = "$[?(@.age==20 || @.name =~ /abc/)]";
94+
Object result = JSONPath.of(path).extract(JSONReader.of(jsonArray));
95+
assertEquals("[{\"name\":\"abc\",\"age\":18},{\"name\":\"def\",\"age\":20},{\"name\":\"xyz\",\"age\":20}]", JSON.toJSONString(result));
96+
}
97+
}

0 commit comments

Comments
 (0)