fix enhance operator in and support nested json path, for issue #177

This commit is contained in:
yanxutao89 2025-05-14 02:21:17 +08:00
parent ab36a7104c
commit 483e9ac232
6 changed files with 177 additions and 90 deletions

View File

@ -7,6 +7,7 @@ import com.alibaba.fastjson2.writer.FieldWriter;
import com.alibaba.fastjson2.writer.ObjectWriter; import com.alibaba.fastjson2.writer.ObjectWriter;
import com.alibaba.fastjson2.writer.ObjectWriterAdapter; import com.alibaba.fastjson2.writer.ObjectWriterAdapter;
import java.lang.reflect.Array;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
@ -1315,7 +1316,7 @@ abstract class JSONPathFilter
static final class NameIntInSegment static final class NameIntInSegment
extends NameFilter { extends NameFilter {
private final long[] values; private long[] values;
private final boolean not; private final boolean not;
public NameIntInSegment( public NameIntInSegment(
@ -1393,6 +1394,37 @@ abstract class JSONPathFilter
return not; 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 static final class NameName

View File

@ -188,6 +188,7 @@ final class JSONPathMulti
((JSONPathFilter.NameFilter) segment).excludeArray(); ((JSONPathFilter.NameFilter) segment).excludeArray();
} }
} }
segment.convert(root);
segment.eval(context); segment.eval(context);
} }

View File

@ -640,6 +640,7 @@ class JSONPathParser {
String fieldName = null; String fieldName = null;
long hashCode = 0; long hashCode = 0;
boolean hasOperator = false;
if (at) { if (at) {
if (jsonReader.ch == '[') { if (jsonReader.ch == '[') {
JSONPathSegment segment = parseArrayAccess(); JSONPathSegment segment = parseArrayAccess();
@ -687,106 +688,114 @@ class JSONPathParser {
return segment; return segment;
} }
} else { } else {
jsonReader.next(); char ch = jsonReader.ch;
} if (ch == 'i' || ch == 'I') {
} hasOperator = true;
} else {
if (fieldName == null) { jsonReader.next();
hashCode = jsonReader.readFieldNameHashCodeUnquote();
fieldName = jsonReader.getFieldName();
}
if (parentheses) {
if (jsonReader.nextIfMatch(')')) {
if (filterNests > 0) {
filterNests--;
} }
return new JSONPathFilter.NameExistsFilter(fieldName, hashCode);
} }
} }
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 (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 (fieldName2 == null && !parentheses
&& (jsonReader.ch == ']' || jsonReader.ch == '|' || jsonReader.ch == '&')
) {
return new JSONPathFilter.NameExistsFilter(fieldName, hashCode);
}
JSONPathFilter.Operator operator = null; JSONPathFilter.Operator operator = null;
Function function = null; Function function = null;
if (jsonReader.ch == '(') { String functionName = null;
if (functionName == null) { long[] hashCode2 = null;
functionName = fieldName; String[] fieldName2 = null;
fieldName = null; if (!hasOperator) {
if (fieldName == null) {
hashCode = jsonReader.readFieldNameHashCodeUnquote();
fieldName = jsonReader.getFieldName();
} }
switch (functionName) { if (parentheses) {
case "type": if (jsonReader.nextIfMatch(')')) {
hashCode = 0; if (filterNests > 0) {
function = JSONPathFunction.TypeFunction.INSTANCE; filterNests--;
break; }
case "size": return new JSONPathFilter.NameExistsFilter(fieldName, hashCode);
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 == '[') { while (jsonReader.ch == '.') {
jsonReader.next();
if (jsonReader.ch == '?') {
jsonReader.next(); jsonReader.next();
JSONPathFilter subFilter = (JSONPathFilter) parseFilter(); long hash = jsonReader.readFieldNameHashCodeUnquote();
function = new JSONPathFunction.FilterFunction(subFilter); String str = jsonReader.getFieldName();
} else {
int index = jsonReader.readInt32Value(); if (jsonReader.ch == '(') {
function = new JSONPathFunction.IndexValue(index); 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;
}
} }
if (!jsonReader.nextIfMatch(']')) {
throw new JSONException("syntax error"); if (fieldName2 == null && !parentheses
} && (jsonReader.ch == ']' || jsonReader.ch == '|' || jsonReader.ch == '&')
} ) {
if (operator == null) {
if (parentheses && jsonReader.nextIfMatch(')')) {
return new JSONPathFilter.NameExistsFilter(fieldName, hashCode); return new JSONPathFilter.NameExistsFilter(fieldName, hashCode);
} }
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 == '?') {
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);
}
operator = JSONPath.parseOperator(jsonReader);
}
} else {
operator = JSONPath.parseOperator(jsonReader); operator = JSONPath.parseOperator(jsonReader);
} }
@ -820,12 +829,30 @@ class JSONPathParser {
} }
case IN: case IN:
case NOT_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")); throw new JSONException(jsonReader.info("jsonpath syntax error"));
} }
jsonReader.next(); jsonReader.next();
JSONPathSegment segment;
if (jsonReader.isString()) { if (jsonReader.isString()) {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
while (jsonReader.isString()) { while (jsonReader.isString()) {
@ -853,14 +880,14 @@ class JSONPathParser {
throw new JSONException(jsonReader.info("jsonpath syntax error")); throw new JSONException(jsonReader.info("jsonpath syntax error"));
} }
if (!jsonReader.nextIfMatch(')')) { if (!(jsonReader.nextIfMatch(')') || jsonReader.nextIfMatch(']'))) {
throw new JSONException(jsonReader.info("jsonpath syntax error")); throw new JSONException(jsonReader.info("jsonpath syntax error"));
} }
if (jsonReader.ch == '&' || jsonReader.ch == '|' || jsonReader.ch == 'a' || jsonReader.ch == 'o') { if (jsonReader.ch == '&' || jsonReader.ch == '|' || jsonReader.ch == 'a' || jsonReader.ch == 'o') {
filterNests--; filterNests--;
segment = parseFilterRest(segment); segment = parseFilterRest(segment);
} }
if (!jsonReader.nextIfMatch(')')) { if (!(jsonReader.nextIfMatch(')') || jsonReader.nextIfMatch(']'))) {
throw new JSONException(jsonReader.info("jsonpath syntax error")); throw new JSONException(jsonReader.info("jsonpath syntax error"));
} }

View File

@ -25,6 +25,8 @@ import static com.alibaba.fastjson2.JSONB.Constants.BC_OBJECT_END;
import static com.alibaba.fastjson2.JSONReader.EOI; import static com.alibaba.fastjson2.JSONReader.EOI;
abstract class JSONPathSegment { abstract class JSONPathSegment {
private JSONPath parentPath;
public abstract void accept(JSONReader jsonReader, JSONPath.Context context); public abstract void accept(JSONReader jsonReader, JSONPath.Context context);
public abstract void eval(JSONPath.Context context); public abstract void eval(JSONPath.Context context);
@ -54,6 +56,18 @@ abstract class JSONPathSegment {
set(context, 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 { interface EvalSegment {
} }

View File

@ -95,6 +95,7 @@ class JSONPathTwoSegment
} }
Context context1 = new Context(this, context0, second, null, 0); Context context1 = new Context(this, context0, second, null, 0);
second.convert(root);
second.eval(context1); second.eval(context1);
Object contextValue = context1.value; Object contextValue = context1.value;
if ((features & Feature.AlwaysReturnList.mask) != 0) { if ((features & Feature.AlwaysReturnList.mask) != 0) {

View File

@ -6,6 +6,8 @@ import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONReader;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Issue117 { public class Issue117 {
@Test @Test
public void test() { public void test() {
@ -23,4 +25,14 @@ public class Issue117 {
System.out.println(times); 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());
}
} }