From 483e9ac232ef1e6f1f8e45b0c3422629c8bc4b05 Mon Sep 17 00:00:00 2001 From: yanxutao89 <910135896@qq.com> Date: Wed, 14 May 2025 02:21:17 +0800 Subject: [PATCH] fix enhance operator in and support nested json path, for issue #177 --- .../com/alibaba/fastjson2/JSONPathFilter.java | 34 +++- .../com/alibaba/fastjson2/JSONPathMulti.java | 1 + .../com/alibaba/fastjson2/JSONPathParser.java | 191 ++++++++++-------- .../alibaba/fastjson2/JSONPathSegment.java | 14 ++ .../alibaba/fastjson2/JSONPathTwoSegment.java | 1 + .../alibaba/fastjson2/issues/Issue117.java | 12 ++ 6 files changed, 170 insertions(+), 83 deletions(-) diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONPathFilter.java b/core/src/main/java/com/alibaba/fastjson2/JSONPathFilter.java index 778ca5b424..3130e9f419 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONPathFilter.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONPathFilter.java @@ -7,6 +7,7 @@ import com.alibaba.fastjson2.writer.ObjectWriter; import com.alibaba.fastjson2.writer.ObjectWriterAdapter; +import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; @@ -1315,7 +1316,7 @@ public boolean apply(Object fieldValue) { static final class NameIntInSegment extends NameFilter { - private final long[] values; + private long[] values; private final boolean not; public NameIntInSegment( @@ -1393,6 +1394,37 @@ public boolean apply(Object fieldValue) { return not; } + + @Override + public void convert(Object root) { + JSONPath parentPath = getParentPath(); + if (parentPath != null) { + Object eval = parentPath.eval(root); + int length = 1; + if (eval != null) { + if (eval.getClass().isArray()) { + length = Array.getLength(eval); + values = new long[length]; + for (int i = 0; i < length; ++i) { + values[i] = Long.parseLong(Array.get(eval, i).toString()); + } + } else if (Collection.class.isAssignableFrom(eval.getClass())) { + Long[] longs = new ArrayList<>((Collection) eval).stream() + .filter(Objects::nonNull) + .map(v -> Long.parseLong(v.toString())) + .toArray(Long[]::new); + length = longs.length; + values = new long[length]; + for (int i = 0; i < length; ++i) { + values[i] = longs[i]; + } + } else { + values = new long[length]; + values[0] = Long.parseLong(eval.toString()); + } + } + } + } } static final class NameName diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONPathMulti.java b/core/src/main/java/com/alibaba/fastjson2/JSONPathMulti.java index ab8b6eb79f..36c0fcee30 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONPathMulti.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONPathMulti.java @@ -188,6 +188,7 @@ public Object eval(Object root) { ((JSONPathFilter.NameFilter) segment).excludeArray(); } } + segment.convert(root); segment.eval(context); } diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONPathParser.java b/core/src/main/java/com/alibaba/fastjson2/JSONPathParser.java index 9a8f0af6cb..e8f55db023 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONPathParser.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONPathParser.java @@ -640,6 +640,7 @@ JSONPathSegment parseFilter() { String fieldName = null; long hashCode = 0; + boolean hasOperator = false; if (at) { if (jsonReader.ch == '[') { JSONPathSegment segment = parseArrayAccess(); @@ -687,106 +688,114 @@ JSONPathSegment parseFilter() { return segment; } } else { - jsonReader.next(); - } - } - - if (fieldName == null) { - hashCode = jsonReader.readFieldNameHashCodeUnquote(); - fieldName = jsonReader.getFieldName(); - } - - if (parentheses) { - if (jsonReader.nextIfMatch(')')) { - if (filterNests > 0) { - filterNests--; + char ch = jsonReader.ch; + if (ch == 'i' || ch == 'I') { + hasOperator = true; + } else { + jsonReader.next(); } - return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); } } + JSONPathFilter.Operator operator = null; + Function function = null; String functionName = null; - long[] hashCode2 = null; String[] fieldName2 = null; - while (jsonReader.ch == '.') { - jsonReader.next(); - long hash = jsonReader.readFieldNameHashCodeUnquote(); - String str = jsonReader.getFieldName(); - - if (jsonReader.ch == '(') { - functionName = str; - break; + if (!hasOperator) { + if (fieldName == null) { + hashCode = jsonReader.readFieldNameHashCodeUnquote(); + fieldName = jsonReader.getFieldName(); } - if (hashCode2 == null) { - hashCode2 = new long[]{hash}; - fieldName2 = new String[]{str}; - } else { - hashCode2 = Arrays.copyOf(hashCode2, hashCode2.length + 1); - hashCode2[hashCode2.length - 1] = hash; - fieldName2 = Arrays.copyOf(fieldName2, fieldName2.length + 1); - fieldName2[fieldName2.length - 1] = str; + if (parentheses) { + if (jsonReader.nextIfMatch(')')) { + if (filterNests > 0) { + filterNests--; + } + return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); + } } - } - if (fieldName2 == null && !parentheses - && (jsonReader.ch == ']' || jsonReader.ch == '|' || jsonReader.ch == '&') - ) { - return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); - } + while (jsonReader.ch == '.') { + jsonReader.next(); + long hash = jsonReader.readFieldNameHashCodeUnquote(); + String str = jsonReader.getFieldName(); - JSONPathFilter.Operator operator = null; - Function function = null; - if (jsonReader.ch == '(') { - if (functionName == null) { - functionName = fieldName; - fieldName = null; + if (jsonReader.ch == '(') { + functionName = str; + break; + } + + if (hashCode2 == null) { + hashCode2 = new long[]{hash}; + fieldName2 = new String[]{str}; + } else { + hashCode2 = Arrays.copyOf(hashCode2, hashCode2.length + 1); + hashCode2[hashCode2.length - 1] = hash; + fieldName2 = Arrays.copyOf(fieldName2, fieldName2.length + 1); + fieldName2[fieldName2.length - 1] = str; + } } - switch (functionName) { - case "type": - hashCode = 0; - function = JSONPathFunction.TypeFunction.INSTANCE; - break; - case "size": - hashCode = 0; - function = JSONPathFunction.SizeFunction.INSTANCE; - break; - case "contains": - hashCode = 0; - operator = JSONPathFilter.Operator.CONTAINS; - break; - default: - throw new JSONException("syntax error, function not support " + fieldName); + if (fieldName2 == null && !parentheses + && (jsonReader.ch == ']' || jsonReader.ch == '|' || jsonReader.ch == '&') + ) { + return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); } - if (function != null) { - jsonReader.next(); - if (!jsonReader.nextIfMatch(')')) { - throw new JSONException("syntax error, function " + functionName); + if (jsonReader.ch == '(') { + if (functionName == null) { + functionName = fieldName; + fieldName = null; + } + + switch (functionName) { + case "type": + hashCode = 0; + function = JSONPathFunction.TypeFunction.INSTANCE; + break; + case "size": + hashCode = 0; + function = JSONPathFunction.SizeFunction.INSTANCE; + break; + case "contains": + hashCode = 0; + operator = JSONPathFilter.Operator.CONTAINS; + break; + default: + throw new JSONException("syntax error, function not support " + fieldName); + } + + if (function != null) { + jsonReader.next(); + if (!jsonReader.nextIfMatch(')')) { + throw new JSONException("syntax error, function " + functionName); + } } } - } - if (function == null && jsonReader.ch == '[') { - jsonReader.next(); - if (jsonReader.ch == '?') { + if (function == null && jsonReader.ch == '[') { jsonReader.next(); - JSONPathFilter subFilter = (JSONPathFilter) parseFilter(); - function = new JSONPathFunction.FilterFunction(subFilter); - } else { - int index = jsonReader.readInt32Value(); - function = new JSONPathFunction.IndexValue(index); - } - if (!jsonReader.nextIfMatch(']')) { - throw new JSONException("syntax error"); + if (jsonReader.ch == '?') { + jsonReader.next(); + JSONPathFilter subFilter = (JSONPathFilter) parseFilter(); + function = new JSONPathFunction.FilterFunction(subFilter); + } else { + int index = jsonReader.readInt32Value(); + function = new JSONPathFunction.IndexValue(index); + } + if (!jsonReader.nextIfMatch(']')) { + throw new JSONException("syntax error"); + } } - } - if (operator == null) { - if (parentheses && jsonReader.nextIfMatch(')')) { - return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); + if (operator == null) { + if (parentheses && jsonReader.nextIfMatch(')')) { + return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); + } + operator = JSONPath.parseOperator(jsonReader); } + } else { operator = JSONPath.parseOperator(jsonReader); } @@ -820,12 +829,30 @@ JSONPathSegment parseFilter() { } case IN: case NOT_IN: { - if (jsonReader.ch != '(') { + JSONPathSegment segment; + char ch = jsonReader.ch; + if (ch == '$') { + StringBuilder sb = new StringBuilder(); + int filterNests = 0; + while (ch != ')' || filterNests != 0) { + sb.append(ch); + if (ch == '(') { + filterNests++; + } else if (ch == ')') { + filterNests--; + } + jsonReader.next(); + ch = jsonReader.ch; + } + jsonReader.next(); + JSONPath parentPath = JSONPath.of(sb.toString()); + return new JSONPathFilter.NameIntInSegment(fieldName, hashCode, fieldName2, hashCode2, function, null, operator == JSONPathFilter.Operator.NOT_IN).setParentPath(parentPath); + } + if (!(ch == '(' || ch == '[')) { throw new JSONException(jsonReader.info("jsonpath syntax error")); } jsonReader.next(); - JSONPathSegment segment; if (jsonReader.isString()) { List list = new ArrayList<>(); while (jsonReader.isString()) { @@ -853,14 +880,14 @@ JSONPathSegment parseFilter() { throw new JSONException(jsonReader.info("jsonpath syntax error")); } - if (!jsonReader.nextIfMatch(')')) { + if (!(jsonReader.nextIfMatch(')') || jsonReader.nextIfMatch(']'))) { throw new JSONException(jsonReader.info("jsonpath syntax error")); } if (jsonReader.ch == '&' || jsonReader.ch == '|' || jsonReader.ch == 'a' || jsonReader.ch == 'o') { filterNests--; segment = parseFilterRest(segment); } - if (!jsonReader.nextIfMatch(')')) { + if (!(jsonReader.nextIfMatch(')') || jsonReader.nextIfMatch(']'))) { throw new JSONException(jsonReader.info("jsonpath syntax error")); } diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONPathSegment.java b/core/src/main/java/com/alibaba/fastjson2/JSONPathSegment.java index 1127990b4a..efaaedf813 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONPathSegment.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONPathSegment.java @@ -25,6 +25,8 @@ import static com.alibaba.fastjson2.JSONReader.EOI; abstract class JSONPathSegment { + private JSONPath parentPath; + public abstract void accept(JSONReader jsonReader, JSONPath.Context context); public abstract void eval(JSONPath.Context context); @@ -54,6 +56,18 @@ public void setLong(JSONPath.Context context, long value) { set(context, value); } + public JSONPath getParentPath() { + return parentPath; + } + + public JSONPathSegment setParentPath(JSONPath parentPath) { + this.parentPath = parentPath; + return this; + } + + public void convert(Object root) { + } + interface EvalSegment { } diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONPathTwoSegment.java b/core/src/main/java/com/alibaba/fastjson2/JSONPathTwoSegment.java index 79201cb242..464fd842ad 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONPathTwoSegment.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONPathTwoSegment.java @@ -95,6 +95,7 @@ public Object eval(Object root) { } Context context1 = new Context(this, context0, second, null, 0); + second.convert(root); second.eval(context1); Object contextValue = context1.value; if ((features & Feature.AlwaysReturnList.mask) != 0) { diff --git a/core/src/test/java/com/alibaba/fastjson2/issues/Issue117.java b/core/src/test/java/com/alibaba/fastjson2/issues/Issue117.java index 79eb75c9c0..dcb43c03c7 100644 --- a/core/src/test/java/com/alibaba/fastjson2/issues/Issue117.java +++ b/core/src/test/java/com/alibaba/fastjson2/issues/Issue117.java @@ -6,6 +6,8 @@ import com.alibaba.fastjson2.JSONReader; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class Issue117 { @Test public void test() { @@ -23,4 +25,14 @@ public void test() { System.out.println(times); } + + @Test + public void test2() { + String text = ("{code:1,msg:'Hello world',data:{list:[1,2,3,4,5], ary2:[{a:2},{a:[2]},{a:3,b:{c:'ddd'}}]}}"); + JSONObject object = JSON.parseObject(text, JSONReader.Feature.AllowUnQuotedFieldNames); + assertEquals("[2]", JSONPath.eval(object, "$.data.list[?(@ in (2))]").toString()); + assertEquals("[2]", JSONPath.eval(object, "$.data.list[?(@ in [2])]").toString()); + assertEquals("[2]", JSONPath.eval(object, "$.data.list[?(@ in $..ary2[0].a)]").toString()); + assertEquals("[2]", JSONPath.eval(object, "$.data.list[?(@ in $..ary2[1].a)]").toString()); + } }