JSONPath.java
package com.alibaba.fastjson2;
import com.alibaba.fastjson2.reader.*;
import com.alibaba.fastjson2.writer.ObjectWriter;
import com.alibaba.fastjson2.writer.ObjectWriterAdapter;
import com.alibaba.fastjson2.writer.ObjectWriterProvider;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.time.ZoneId;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Represents a JSONPath expression for querying and manipulating JSON data.
*
* <p>JSONPath is a query language for JSON, similar to XPath for XML.
* It allows you to select and extract data from a JSON document.
*
* <p>Example usage:
* <pre>{@code
* // Parse JSON string and extract value using JSONPath
* String json = "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}";
* Object result = JSONPath.extract(json, "$.name");
* // result = "John"
*
* // Evaluate JSONPath on an object
* Map<String, Object> map = new HashMap<>();
* map.put("name", "John");
* map.put("age", 30);
* Object result = JSONPath.eval(map, "$.name");
* // result = "John"
*
* // Set value using JSONPath
* JSONPath.set(map, "$.name", "Jane");
* // map.get("name") = "Jane"
*
* // Remove value using JSONPath
* JSONPath.remove(map, "$.age");
* // map.size() = 1 (age field removed)
*
* // Check if path exists
* boolean exists = JSONPath.contains(map, "$.name");
* // exists = true
* }</pre>
*
* <p>Supported JSONPath expressions:
* <ul>
* <li><code>$</code> - Root object/element</li>
* <li><code>@</code> - Current object/element</li>
* <li><code>. or []</code> - Child operator</li>
* <li><code>..</code> - Recursive descent</li>
* <li><code>*</code> - Wildcard</li>
* <li><code>[]</code> - Array index or filter</li>
* <li><code>[start:end:step]</code> - Array slice</li>
* <li><code>?()</code> - Filter expression</li>
* </ul>
*
* @see <a href="https://github.com/json-path/JsonPath">JSONPath specification</a>
* @since 2.0.0
*/
public abstract class JSONPath {
static final JSONReader.Context PARSE_CONTEXT = JSONFactory.createReadContext();
/**
* Context for JSON reading operations
*/
JSONReader.Context readerContext;
/**
* Context for JSON writing operations
*/
JSONWriter.Context writerContext;
/**
* The JSONPath expression string
*/
final String path;
/**
* Feature flags for this JSONPath
*/
final long features;
/**
* Constructs a JSONPath with the specified path and features
*
* @param path the JSONPath expression
* @param features the features to apply
*/
protected JSONPath(String path, Feature... features) {
this.path = path;
long featuresValue = 0;
for (Feature feature : features) {
featuresValue |= feature.mask;
}
this.features = featuresValue;
}
/**
* Constructs a JSONPath with the specified path and feature flags
*
* @param path the JSONPath expression
* @param features the feature flags to apply
*/
protected JSONPath(String path, long features) {
this.path = path;
this.features = features;
}
/**
* Gets the parent JSONPath of this path
*
* @return the parent JSONPath, or null if this is the root path
*/
public abstract JSONPath getParent();
/**
* Checks if this path ends with a filter
*
* @return true if this path ends with a filter, false otherwise
*/
public boolean endsWithFilter() {
return false;
}
/**
* Checks if this path represents a previous reference
*
* @return true if this path is a previous reference, false otherwise
*/
public boolean isPrevious() {
return false;
}
/**
* Returns the string representation of this JSONPath
*
* @return the JSONPath expression string
*/
@Override
public final String toString() {
return path;
}
/**
* Extracts a value from JSON string using the specified path
*
* @param json the JSON string to extract from
* @param path the JSONPath expression
* @return the extracted value, or null if not found
*/
public static Object extract(String json, String path) {
JSONReader jsonReader = JSONReader.of(json);
JSONPath jsonPath = JSONPath.of(path);
return jsonPath.extract(jsonReader);
}
/**
* Extracts a value from JSON string using the specified path and features
*
* @param json the JSON string to extract from
* @param path the JSONPath expression
* @param features the features to apply during extraction
* @return the extracted value, or null if not found
*/
public static Object extract(String json, String path, Feature... features) {
JSONReader jsonReader = JSONReader.of(json);
JSONPath jsonPath = JSONPath.of(path, features);
return jsonPath.extract(jsonReader);
}
/**
* Evaluates the JSONPath expression on a JSON string
*
* @param str the JSON string to evaluate
* @param path the JSONPath expression
* @return the evaluated result, or null if not found
*/
public static Object eval(String str, String path) {
return extract(str, path);
}
/**
* Evaluates the JSONPath expression on an object
*
* @param rootObject the root object to evaluate
* @param path the JSONPath expression
* @return the evaluated result, or null if not found
*/
public static Object eval(Object rootObject, String path) {
return JSONPath.of(path)
.eval(rootObject);
}
/**
* Sets a value in a JSON string using the specified path and returns the modified JSON
*
* @param json the JSON string to modify
* @param path the JSONPath expression
* @param value the value to set
* @return the modified JSON string
*/
public static String set(String json, String path, Object value) {
Object object = JSON.parse(json);
JSONPath.of(path)
.set(object, value);
return JSON.toJSONString(object);
}
/**
* Checks if the specified path exists in the object
*
* @param rootObject the root object to check
* @param path the JSONPath expression
* @return true if the path exists, false otherwise
*/
public static boolean contains(Object rootObject, String path) {
if (rootObject == null) {
return false;
}
JSONPath jsonPath = JSONPath.of(path);
return jsonPath.contains(rootObject);
}
/**
* Sets a value in an object using the specified path
*
* @param rootObject the root object to modify
* @param path the JSONPath expression
* @param value the value to set
* @return the modified root object
*/
public static Object set(Object rootObject, String path, Object value) {
JSONPath.of(path)
.set(rootObject, value);
return rootObject;
}
/**
* Sets a callback function for the specified path in an object
*
* @param rootObject the root object to modify
* @param path the JSONPath expression
* @param callback the callback function to set
* @return the modified root object
*/
public static Object setCallback(Object rootObject, String path, Function callback) {
JSONPath.of(path)
.setCallback(rootObject, callback);
return rootObject;
}
/**
* Sets a callback function for the specified path in an object
*
* @param rootObject the root object to modify
* @param path the JSONPath expression
* @param callback the callback function to set
* @return the modified root object
*/
public static Object setCallback(Object rootObject, String path, BiFunction callback) {
JSONPath.of(path)
.setCallback(rootObject, callback);
return rootObject;
}
/**
* Removes a value from a JSON string using the specified path and returns the modified JSON
*
* @param json the JSON string to modify
* @param path the JSONPath expression
* @return the modified JSON string
*/
public static String remove(String json, String path) {
Object object = JSON.parse(json);
JSONPath.of(path)
.remove(object);
return JSON.toJSONString(object);
}
/**
* Removes a value from an object using the specified path
*
* @param rootObject the root object to modify
* @param path the JSONPath expression
*/
public static void remove(Object rootObject, String path) {
JSONPath.of(path)
.remove(rootObject);
}
/**
* Gets all paths in the object
*
* @param javaObject the object to get paths from
* @return a map of paths to values
*/
public static Map<String, Object> paths(Object javaObject) {
Map<Object, String> values = new IdentityHashMap<>();
Map<String, Object> paths = new LinkedHashMap<>();
RootPath.INSTANCE.paths(values, paths, "$", javaObject);
return paths;
}
/**
* Recursively collects paths and values from the object
*
* @param values map to store object to path mappings
* @param paths map to store path to value mappings
* @param parent the parent path
* @param javaObject the object to process
*/
void paths(Map<Object, String> values, Map paths, String parent, Object javaObject) {
if (javaObject == null) {
return;
}
String p = values.put(javaObject, parent);
if (p != null) {
Class<?> type = javaObject.getClass();
boolean basicType = type == String.class
|| type == Boolean.class
|| type == Character.class
|| type == UUID.class
|| javaObject instanceof Enum
|| javaObject instanceof Number
|| javaObject instanceof Date;
if (!basicType) {
return;
}
}
paths.put(parent, javaObject);
if (javaObject instanceof Map) {
Map map = (Map) javaObject;
for (Object entryObj : map.entrySet()) {
Map.Entry entry = (Map.Entry) entryObj;
Object key = entry.getKey();
String path;
if (key instanceof String) {
String strKey = (String) key;
boolean escape = strKey.isEmpty();
if (!escape) {
char c0 = strKey.charAt(0);
escape = !((c0 >= 'a' && c0 <= 'z') || (c0 >= 'A' && c0 <= 'Z') || c0 == '_');
if (!escape) {
for (int i = 1; i < strKey.length(); i++) {
char ch = strKey.charAt(i);
escape = !((ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| ch == '_');
if (escape) {
break;
}
}
}
}
if (escape) {
path = parent + '[' + JSON.toJSONString(strKey, JSONWriter.Feature.UseSingleQuotes) + ']';
} else {
path = parent + "." + strKey;
}
paths(values, paths, path, entry.getValue());
}
}
return;
}
if (javaObject instanceof Collection) {
Collection collection = (Collection) javaObject;
int i = 0;
for (Object item : collection) {
String path = parent + "[" + i + "]";
paths(values, paths, path, item);
++i;
}
return;
}
Class<?> clazz = javaObject.getClass();
if (clazz.isArray()) {
int len = Array.getLength(javaObject);
for (int i = 0; i < len; ++i) {
Object item = Array.get(javaObject, i);
String path = parent + "[" + i + "]";
paths(values, paths, path, item);
}
return;
}
if (ObjectWriterProvider.isPrimitiveOrEnum(clazz)) {
return;
}
ObjectWriter serializer = getWriterContext().getObjectWriter(clazz);
if (serializer instanceof ObjectWriterAdapter) {
ObjectWriterAdapter javaBeanSerializer = (ObjectWriterAdapter) serializer;
try {
Map<String, Object> fieldValues = javaBeanSerializer.toMap(javaObject);
for (Map.Entry<String, Object> entry : fieldValues.entrySet()) {
String key = entry.getKey();
if (key != null) {
String path = parent + "." + key;
paths(values, paths, path, entry.getValue());
}
}
} catch (Exception e) {
throw new JSONException("toJSON error", e);
}
}
}
/**
* Checks if this path is a reference
*
* @return true if this path is a reference, false otherwise
*/
public abstract boolean isRef();
/**
* Adds values to an array at the specified root object
*
* @param root the root object
* @param values the values to add
*/
public void arrayAdd(Object root, Object... values) {
Object result = eval(root);
if (result == null) {
set(root, JSONArray.of(values));
return;
}
if (result instanceof Collection) {
Collection collection = (Collection) result;
collection.addAll(Arrays.asList(values));
}
}
/**
* Checks if the path exists in the object
*
* @param object the object to check
* @return true if the path exists, false otherwise
*/
public abstract boolean contains(Object object);
/**
* Evaluates the path on the object
*
* @param object the object to evaluate
* @return the evaluation result
*/
public abstract Object eval(Object object);
/**
* Creates a new reading context
*
* @return a new JSONReader.Context
*/
protected JSONReader.Context createContext() {
return JSONFactory.createReadContext();
}
/**
* Extracts a value from a JSON string
*
* @param jsonStr the JSON string to extract from
* @return the extracted value, or null if input is null
*/
public Object extract(String jsonStr) {
if (jsonStr == null) {
return null;
}
try (JSONReader jsonReader = JSONReader.of(jsonStr, createContext())) {
return extract(jsonReader);
}
}
/**
* Extracts a value from a JSON byte array
*
* @param jsonBytes the JSON bytes to extract from
* @return the extracted value, or null if input is null
*/
public Object extract(byte[] jsonBytes) {
if (jsonBytes == null) {
return null;
}
try (JSONReader jsonReader = JSONReader.of(jsonBytes, createContext())) {
return extract(jsonReader);
}
}
/**
* Extracts a value from a JSON byte array with specified offset, length and charset
*
* @param jsonBytes the JSON bytes to extract from
* @param off the offset in the byte array
* @param len the number of bytes to read
* @param charset the charset to use
* @return the extracted value, or null if input is null
*/
public Object extract(byte[] jsonBytes, int off, int len, Charset charset) {
if (jsonBytes == null) {
return null;
}
try (JSONReader jsonReader = JSONReader.of(jsonBytes, off, len, charset, createContext())) {
return extract(jsonReader);
}
}
/**
* Extracts a value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted value
*/
public abstract Object extract(JSONReader jsonReader);
/**
* Extracts a scalar value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted scalar value
*/
public abstract String extractScalar(JSONReader jsonReader);
/**
* Gets the reading context, creating it if necessary
*
* @return the JSONReader.Context
*/
public JSONReader.Context getReaderContext() {
if (readerContext == null) {
readerContext = JSONFactory.createReadContext();
}
return readerContext;
}
/**
* Sets the reading context
*
* @param context the context to set
* @return this JSONPath instance
*/
public JSONPath setReaderContext(JSONReader.Context context) {
this.readerContext = context;
return this;
}
/**
* Gets the writing context, creating it if necessary
*
* @return the JSONWriter.Context
*/
public JSONWriter.Context getWriterContext() {
if (writerContext == null) {
writerContext = JSONFactory.createWriteContext();
}
return writerContext;
}
/**
* Sets the writing context
*
* @param writerContext the context to set
* @return this JSONPath instance
*/
public JSONPath setWriterContext(JSONWriter.Context writerContext) {
this.writerContext = writerContext;
return this;
}
/**
* Sets a value in the object
*
* @param object the object to modify
* @param value the value to set
*/
public abstract void set(Object object, Object value);
/**
* Sets a value in the object with specified reader features
*
* @param object the object to modify
* @param value the value to set
* @param readerFeatures the reader features to apply
*/
public abstract void set(Object object, Object value, JSONReader.Feature... readerFeatures);
/**
* Sets a callback function for the object
*
* @param object the object to modify
* @param callback the callback function to set
*/
public void setCallback(Object object, Function callback) {
setCallback(
object,
new JSONPathFunction.BiFunctionAdapter(callback)
);
}
/**
* Sets a callback function for the object
*
* @param object the object to modify
* @param callback the callback function to set
*/
public abstract void setCallback(Object object, BiFunction callback);
/**
* Sets an integer value in the object
*
* @param object the object to modify
* @param value the integer value to set
*/
public abstract void setInt(Object object, int value);
/**
* Sets a long value in the object
*
* @param object the object to modify
* @param value the long value to set
*/
public abstract void setLong(Object object, long value);
/**
* Removes a value from the object
*
* @param object the object to modify
* @return true if removal was successful, false otherwise
*/
public abstract boolean remove(Object object);
/**
* Extracts a value using the provided JSONReader and passes it to the consumer
*
* @param jsonReader the JSONReader to use
* @param consumer the consumer to accept the extracted value
*/
public void extract(JSONReader jsonReader, ValueConsumer consumer) {
Object object = extract(jsonReader);
if (object == null) {
consumer.acceptNull();
return;
}
if (object instanceof Number) {
consumer.accept((Number) object);
return;
}
if (object instanceof String) {
consumer.accept((String) object);
return;
}
if (object instanceof Boolean) {
consumer.accept((Boolean) object);
return;
}
if (object instanceof Map) {
consumer.accept((Map) object);
return;
}
if (object instanceof List) {
consumer.accept((List) object);
return;
}
throw new JSONException("TODO : " + object.getClass());
}
/**
* Extracts a scalar value using the provided JSONReader and passes it to the consumer
*
* @param jsonReader the JSONReader to use
* @param consumer the consumer to accept the extracted scalar value
*/
public void extractScalar(JSONReader jsonReader, ValueConsumer consumer) {
Object object = extractScalar(jsonReader);
if (object == null) {
consumer.acceptNull();
return;
}
String str = object.toString();
consumer.accept(str);
}
/**
* Extracts a Long value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted Long value, or null if the value was null
*/
public Long extractInt64(JSONReader jsonReader) {
long value = extractInt64Value(jsonReader);
if (jsonReader.wasNull) {
return null;
}
return value;
}
/**
* Extracts a long value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted long value, or 0 if the value was null
*/
public long extractInt64Value(JSONReader jsonReader) {
Object object = extract(jsonReader);
if (object == null) {
jsonReader.wasNull = true;
return 0L;
}
if (object instanceof Number) {
return ((Number) object).longValue();
}
Function typeConvert = JSONFactory.getDefaultObjectReaderProvider().getTypeConvert(object.getClass(), long.class);
if (typeConvert == null) {
throw new JSONException("can not convert to long : " + object);
}
Object converted = typeConvert.apply(object);
return (Long) converted;
}
/**
* Extracts an Integer value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted Integer value, or null if the value was null
*/
public Integer extractInt32(JSONReader jsonReader) {
int intValue = extractInt32Value(jsonReader);
if (jsonReader.wasNull) {
return null;
}
return intValue;
}
/**
* Extracts an int value using the provided JSONReader
*
* @param jsonReader the JSONReader to use
* @return the extracted int value, or 0 if the value was null
*/
public int extractInt32Value(JSONReader jsonReader) {
Object object = extract(jsonReader);
if (object == null) {
jsonReader.wasNull = true;
return 0;
}
if (object instanceof Number) {
return ((Number) object).intValue();
}
Function typeConvert = JSONFactory.getDefaultObjectReaderProvider().getTypeConvert(object.getClass(), int.class);
if (typeConvert == null) {
throw new JSONException("can not convert to int : " + object);
}
return (Integer) typeConvert.apply(object);
}
/**
* Compiles a JSONPath expression (deprecated, use {@link #of(String)} instead)
*
* @param path the JSONPath expression to compile
* @return the compiled JSONPath
* @deprecated use {@link #of(String)} instead
*/
@Deprecated
public static JSONPath compile(String path) {
return of(path);
}
/**
* Compiles a JSONPath expression for a specific object class
*
* @param strPath the JSONPath expression to compile
* @param objectClass the class of the object to use with this path
* @return the compiled JSONPath
*/
public static JSONPath compile(String strPath, Class objectClass) {
JSONPath path = of(strPath);
JSONFactory.JSONPathCompiler compiler = JSONFactory.getDefaultJSONPathCompiler();
return compiler.compile(objectClass, path);
}
/**
* Creates a single segment JSONPath from a segment
*
* @param segment the segment to create a path from
* @return the created JSONPath
*/
static JSONPathSingle of(JSONPathSegment segment) {
String prefix;
if (segment instanceof JSONPathSegment.MultiIndexSegment || segment instanceof JSONPathSegmentIndex) {
prefix = "$";
} else {
prefix = "$.";
}
String path = prefix + segment.toString();
if (segment instanceof JSONPathSegmentName) {
return new JSONPathSingleName(path, (JSONPathSegmentName) segment);
}
return new JSONPathSingle(segment, path);
}
/**
* Creates a JSONPath from a path string
*
* @param path the JSONPath expression
* @return the created JSONPath
*/
public static JSONPath of(String path) {
if ("#-1".equals(path)) {
return PreviousPath.INSTANCE;
}
return new JSONPathParser(path)
.parse();
}
/**
* Creates a typed JSONPath from a path string and type
*
* @param path the JSONPath expression
* @param type the type of the result
* @return the created JSONPath
*/
public static JSONPath of(String path, Type type) {
JSONPath jsonPath = of(path);
return JSONPathTyped.of(jsonPath, type);
}
/**
* Creates a typed JSONPath from a path string, type and features
*
* @param path the JSONPath expression
* @param type the type of the result
* @param features the features to apply
* @return the created JSONPath
*/
public static JSONPath of(String path, Type type, Feature... features) {
JSONPath jsonPath = of(path, features);
return JSONPathTyped.of(jsonPath, type);
}
/**
* Creates a multi-path JSONPath
*
* @param paths the JSONPath expressions
* @param types the types of the results
* @return the created JSONPath
* @since 2.0.20
*/
public static JSONPath of(String[] paths, Type[] types) {
return of(paths, types, null, null, (ZoneId) null);
}
/**
* Creates a multi-path JSONPath
*
* @param paths the JSONPath expressions
* @param types the types of the results
* @param features the reader features to apply
* @return the created JSONPath
* @since 2.0.20
*/
public static JSONPath of(String[] paths, Type[] types, JSONReader.Feature... features) {
return of(paths, types, null, null, null, features);
}
/**
* Creates a multi-path JSONPath
*
* @param paths the JSONPath expressions
* @param types the types of the results
* @param formats the formats to apply
* @param pathFeatures the path features
* @param zoneId the zone ID
* @param features the reader features to apply
* @return the created JSONPath
* @since 2.0.20
*/
public static JSONPath of(
String[] paths,
Type[] types,
String[] formats,
long[] pathFeatures,
ZoneId zoneId,
JSONReader.Feature... features
) {
if (paths.length == 0) {
throw new JSONException("illegal paths, not support 0 length");
}
if (types == null) {
types = new Type[paths.length];
Arrays.fill(types, Object.class);
}
if (types.length != paths.length) {
throw new JSONException("types.length not equals paths.length");
}
JSONPath[] jsonPaths = new JSONPath[paths.length];
for (int i = 0; i < paths.length; i++) {
jsonPaths[i] = of(paths[i]);
}
boolean allSingleName = true, allSinglePositiveIndex = true;
boolean allTwoName = true, allTwoIndexPositive = true;
boolean allThreeName = true;
boolean sameMultiLength = true;
JSONPathMulti firstMulti = null;
for (int i = 0; i < jsonPaths.length; i++) {
JSONPath path = jsonPaths[i];
if (i == 0) {
if (path instanceof JSONPathMulti) {
firstMulti = (JSONPathMulti) path;
} else {
sameMultiLength = false;
}
} else {
if (sameMultiLength) {
if (path instanceof JSONPathMulti) {
if (((JSONPathMulti) path).segments.size() != firstMulti.segments.size()) {
sameMultiLength = false;
}
}
}
}
if (allSingleName && !(path instanceof JSONPathSingleName)) {
allSingleName = false;
}
if (allSinglePositiveIndex) {
if (!(path instanceof JSONPathSingleIndex)
|| ((JSONPathSingleIndex) path).index < 0) {
allSinglePositiveIndex = false;
}
}
if (allTwoName) {
if (path instanceof JSONPathTwoSegment) {
JSONPathTwoSegment two = (JSONPathTwoSegment) path;
if (!(two.second instanceof JSONPathSegmentName)) {
allTwoName = false;
}
} else {
allTwoName = false;
}
}
if (allTwoIndexPositive) {
if (path instanceof JSONPathTwoSegment) {
JSONPathTwoSegment two = (JSONPathTwoSegment) path;
if (!(two.second instanceof JSONPathSegmentIndex) || ((JSONPathSegmentIndex) two.second).index < 0) {
allTwoIndexPositive = false;
}
} else {
allTwoIndexPositive = false;
}
}
if (allThreeName) {
if (path instanceof JSONPathMulti) {
JSONPathMulti multi = (JSONPathMulti) path;
if (multi.segments.size() == 3) {
JSONPathSegment three = multi.segments.get(2);
if (multi.segments.get(0) instanceof JSONPathSegment.AllSegment
|| multi.segments.get(1) instanceof JSONPathSegment.AllSegment
|| !(three instanceof JSONPathSegmentName)) {
allThreeName = false;
}
} else {
allThreeName = false;
}
} else {
allThreeName = false;
}
}
}
long featuresValue = JSONReader.Feature.of(features);
if (allSingleName) {
return new JSONPathTypedMultiNames(
jsonPaths,
null,
jsonPaths,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
if (allSinglePositiveIndex) {
return new JSONPathTypedMultiIndexes(jsonPaths, null, jsonPaths, types, formats, pathFeatures, zoneId, featuresValue);
}
if (allTwoName || allTwoIndexPositive) {
boolean samePrefix = true;
JSONPathSegment first0 = ((JSONPathTwoSegment) jsonPaths[0]).first;
for (int i = 1; i < jsonPaths.length; i++) {
JSONPathTwoSegment two = (JSONPathTwoSegment) jsonPaths[i];
if (!first0.equals(two.first)) {
samePrefix = false;
break;
}
}
if (samePrefix) {
JSONPath firstPath = jsonPaths[0];
if (allTwoName) {
JSONPathSingleName[] names = new JSONPathSingleName[jsonPaths.length];
for (int i = 0; i < jsonPaths.length; i++) {
JSONPathTwoSegment two = (JSONPathTwoSegment) jsonPaths[i];
JSONPathSegmentName name = (JSONPathSegmentName) two.second;
names[i] = new JSONPathSingleName("$." + name, name);
}
String prefixPath = firstPath.path.substring(0, firstPath.path.length() - names[0].name.length() - 1);
if (first0 instanceof JSONPathSegmentName) {
JSONPathSegmentName name = (JSONPathSegmentName) first0;
JSONPath prefix = new JSONPathSingleName(prefixPath, name);
return new JSONPathTypedMultiNamesPrefixName1(
jsonPaths,
prefix,
names,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
} else if (first0 instanceof JSONPathSegmentIndex) {
JSONPathSegmentIndex first0Index = ((JSONPathSegmentIndex) first0);
if (first0Index.index >= 0) {
JSONPathSingleIndex prefix = new JSONPathSingleIndex(prefixPath, first0Index);
return new JSONPathTypedMultiNamesPrefixIndex1(
jsonPaths,
prefix,
names,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
}
} else {
JSONPathSingleIndex[] indexes = new JSONPathSingleIndex[jsonPaths.length];
for (int i = 0; i < jsonPaths.length; i++) {
JSONPathTwoSegment two = (JSONPathTwoSegment) jsonPaths[i];
JSONPathSegmentIndex name = (JSONPathSegmentIndex) two.second;
indexes[i] = new JSONPathSingleIndex("$" + name, name);
}
JSONPath prefix = null;
if (first0 instanceof JSONPathSegmentName) {
JSONPathSegmentName name = (JSONPathSegmentName) first0;
prefix = new JSONPathSingleName("$." + name.name, name);
} else if (first0 instanceof JSONPathSegmentIndex) {
JSONPathSegmentIndex index = (JSONPathSegmentIndex) first0;
prefix = new JSONPathSingleIndex("$[" + index.index + "]", index);
}
if (prefix != null) {
return new JSONPathTypedMultiIndexes(
jsonPaths,
prefix,
indexes,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
}
}
} else if (allThreeName) {
boolean samePrefix = true;
JSONPathSegment first0 = ((JSONPathMulti) jsonPaths[0]).segments.get(0);
JSONPathSegment first1 = ((JSONPathMulti) jsonPaths[0]).segments.get(1);
for (int i = 1; i < jsonPaths.length; i++) {
JSONPathMulti multi = (JSONPathMulti) jsonPaths[i];
if (!first0.equals(multi.segments.get(0))) {
samePrefix = false;
break;
}
if (!first1.equals(multi.segments.get(1))) {
samePrefix = false;
break;
}
}
if (samePrefix) {
JSONPathSingleName[] names = new JSONPathSingleName[jsonPaths.length];
for (int i = 0; i < jsonPaths.length; i++) {
JSONPathMulti multi = (JSONPathMulti) jsonPaths[i];
JSONPathSegmentName name = (JSONPathSegmentName) multi.segments.get(2);
names[i] = new JSONPathSingleName("$." + name, name);
}
JSONPath firstPath = jsonPaths[0];
String prefixPath = firstPath.path.substring(0, firstPath.path.length() - names[0].name.length() - 1);
JSONPathTwoSegment prefix = new JSONPathTwoSegment(prefixPath, first0, first1);
if (first0 instanceof JSONPathSegmentName && first1 instanceof JSONPathSegmentName) {
return new JSONPathTypedMultiNamesPrefixName2(
jsonPaths,
prefix,
names,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
return new JSONPathTypedMultiNames(
jsonPaths,
prefix,
names,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
}
if (sameMultiLength && paths.length > 1) {
boolean samePrefix = true;
boolean sameType = true;
int lastIndex = firstMulti.segments.size() - 1;
JSONPathSegment lastSegment = firstMulti.segments.get(lastIndex);
for (int i = 0; i < lastIndex; i++) {
JSONPathSegment segment = firstMulti.segments.get(i);
for (int j = 1; j < paths.length; j++) {
JSONPath jsonPath = jsonPaths[j];
JSONPathSegment segment1;
if (jsonPath instanceof JSONPathMulti) {
JSONPathMulti path = (JSONPathMulti) jsonPath;
segment1 = path.segments.get(i);
} else if (jsonPath instanceof JSONPathSingleName) {
segment1 = ((JSONPathSingleName) jsonPath).segment;
} else if (jsonPath instanceof JSONPathSingleIndex) {
segment1 = ((JSONPathSingleIndex) jsonPath).segment;
} else {
segment1 = null;
}
if (!segment.equals(segment1)) {
samePrefix = false;
break;
}
}
if (!samePrefix) {
break;
}
}
if (samePrefix) {
for (int i = 1; i < paths.length; i++) {
JSONPathMulti path = (JSONPathMulti) jsonPaths[i];
if (!lastSegment.getClass().equals(path.segments.get(lastIndex).getClass())) {
sameType = false;
break;
}
}
if (sameType) {
List<JSONPathSegment> prefixSegments = firstMulti.segments.subList(0, lastIndex - 1);
String prefixPath = null;
int dotIndex = firstMulti.path.lastIndexOf('.');
if (dotIndex != -1) {
prefixPath = firstMulti.path.substring(0, dotIndex - 1);
}
if (prefixPath != null) {
JSONPathMulti prefix = new JSONPathMulti(prefixPath, prefixSegments);
if (lastSegment instanceof JSONPathSegmentIndex) {
JSONPath[] indexPaths = new JSONPath[paths.length];
for (int i = 0; i < jsonPaths.length; i++) {
JSONPathMulti path = (JSONPathMulti) jsonPaths[i];
JSONPathSegmentIndex lastSegmentIndex = (JSONPathSegmentIndex) path.segments.get(lastIndex);
indexPaths[i] = new JSONPathSingleIndex(lastSegmentIndex.toString(), lastSegmentIndex);
}
return new JSONPathTypedMultiIndexes(
jsonPaths,
prefix,
indexPaths,
types,
formats,
pathFeatures,
zoneId,
featuresValue
);
}
}
}
}
}
return new JSONPathTypedMulti(jsonPaths, types, formats, pathFeatures, zoneId, featuresValue);
}
/**
* Creates a JSONPath from a path string with specified features
*
* @param path the JSONPath expression
* @param features the features to apply
* @return the created JSONPath
*/
public static JSONPath of(String path, Feature... features) {
if ("#-1".equals(path)) {
return PreviousPath.INSTANCE;
}
return new JSONPathParser(path)
.parse(features);
}
/**
* Parses an operator from the JSONReader
*
* @param jsonReader the JSONReader to parse from
* @return the parsed operator
*/
static JSONPathFilter.Operator parseOperator(JSONReader jsonReader) {
JSONPathFilter.Operator operator;
switch (jsonReader.ch) {
case '<':
jsonReader.next();
if (jsonReader.ch == '=') {
jsonReader.next();
operator = JSONPathFilter.Operator.LE;
} else if (jsonReader.ch == '>') {
jsonReader.next();
operator = JSONPathFilter.Operator.NE;
} else {
operator = JSONPathFilter.Operator.LT;
}
break;
case '=':
jsonReader.next();
if (jsonReader.ch == '~') {
jsonReader.nextWithoutComment();
operator = JSONPathFilter.Operator.REG_MATCH;
} else if (jsonReader.ch == '=') {
jsonReader.next();
operator = JSONPathFilter.Operator.EQ;
} else {
operator = JSONPathFilter.Operator.EQ;
}
break;
case '!':
jsonReader.next();
if (jsonReader.ch == '=') {
jsonReader.next();
operator = JSONPathFilter.Operator.NE;
} else {
throw new JSONException("not support operator : !" + jsonReader.ch);
}
break;
case '>':
jsonReader.next();
if (jsonReader.ch == '=') {
jsonReader.next();
operator = JSONPathFilter.Operator.GE;
} else {
operator = JSONPathFilter.Operator.GT;
}
break;
case 'l':
case 'L': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("like".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.LIKE;
} else {
throw new JSONException("not support operator : " + fieldName);
}
break;
}
case 'n':
case 'N': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("nin".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.NOT_IN;
break;
}
if (!"not".equalsIgnoreCase(fieldName)) {
throw new JSONException("not support operator : " + fieldName);
}
jsonReader.readFieldNameHashCodeUnquote();
fieldName = jsonReader.getFieldName();
if ("like".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.NOT_LIKE;
} else if ("rlike".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.NOT_RLIKE;
} else if ("in".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.NOT_IN;
} else if ("between".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.NOT_BETWEEN;
} else {
throw new JSONException("not support operator : " + fieldName);
}
break;
}
case 'i':
case 'I': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("in".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.IN;
} else if ("is".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.EQ;
} else {
throw new JSONException("not support operator : " + fieldName);
}
break;
}
case 'r':
case 'R': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("rlike".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.RLIKE;
} else {
throw new JSONException("not support operator : " + fieldName);
}
break;
}
case 'b':
case 'B': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("between".equalsIgnoreCase(fieldName)) {
operator = JSONPathFilter.Operator.BETWEEN;
} else {
throw new JSONException("not support operator : " + fieldName);
}
break;
}
case 's':
case 'S': {
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("starts".equalsIgnoreCase(fieldName)) {
jsonReader.readFieldNameHashCodeUnquote();
fieldName = jsonReader.getFieldName();
if (!"with".equalsIgnoreCase(fieldName)) {
throw new JSONException("not support operator : " + fieldName);
}
} else if (!"startsWith".equalsIgnoreCase(fieldName)) {
throw new JSONException("not support operator : " + fieldName);
}
operator = JSONPathFilter.Operator.STARTS_WITH;
break;
}
case 'e':
case 'E':
jsonReader.readFieldNameHashCodeUnquote();
String fieldName = jsonReader.getFieldName();
if ("ends".equalsIgnoreCase(fieldName)) {
jsonReader.readFieldNameHashCodeUnquote();
fieldName = jsonReader.getFieldName();
if (!"with".equalsIgnoreCase(fieldName)) {
throw new JSONException("not support operator : " + fieldName);
}
} else if (!"endsWith".equalsIgnoreCase(fieldName)) {
throw new JSONException("not support operator : " + fieldName);
}
operator = JSONPathFilter.Operator.ENDS_WITH;
break;
default: {
jsonReader.readFieldNameHashCodeUnquote();
throw new JSONException("not support operator : " + jsonReader.getFieldName());
}
}
return operator;
}
/**
* Represents a previous path reference
*/
static final class PreviousPath
extends JSONPath {
static final PreviousPath INSTANCE = new PreviousPath("#-1");
PreviousPath(String path) {
super(path);
}
@Override
public boolean isRef() {
throw new JSONException("unsupported operation");
}
@Override
public boolean isPrevious() {
return true;
}
@Override
public boolean contains(Object rootObject) {
throw new JSONException("unsupported operation");
}
@Override
public Object eval(Object rootObject) {
throw new JSONException("unsupported operation");
}
@Override
public Object extract(JSONReader jsonReader) {
throw new JSONException("unsupported operation");
}
@Override
public String extractScalar(JSONReader jsonReader) {
throw new JSONException("unsupported operation");
}
@Override
public void set(Object rootObject, Object value) {
throw new JSONException("unsupported operation");
}
@Override
public void set(Object rootObject, Object value, JSONReader.Feature... readerFeatures) {
throw new JSONException("unsupported operation");
}
@Override
public void setCallback(Object rootObject, BiFunction callback) {
throw new JSONException("unsupported operation");
}
@Override
public JSONPath getParent() {
throw new JSONException("unsupported operation");
}
@Override
public void setInt(Object rootObject, int value) {
throw new JSONException("unsupported operation");
}
@Override
public void setLong(Object rootObject, long value) {
throw new JSONException("unsupported operation");
}
@Override
public boolean remove(Object rootObject) {
throw new JSONException("unsupported operation");
}
}
/**
* Represents the root path ($)
*/
static final class RootPath
extends JSONPath {
static final RootPath INSTANCE = new RootPath();
private RootPath() {
super("$");
}
@Override
public boolean isRef() {
return true;
}
@Override
public boolean contains(Object object) {
return false;
}
@Override
public Object eval(Object object) {
return object;
}
@Override
public Object extract(JSONReader jsonReader) {
if (jsonReader == null) {
return null;
}
return jsonReader.readAny();
}
@Override
public String extractScalar(JSONReader jsonReader) {
Object any = jsonReader.readAny();
return JSON.toJSONString(any);
}
@Override
public void set(Object object, Object value) {
throw new JSONException("unsupported operation");
}
@Override
public void set(Object object, Object value, JSONReader.Feature... readerFeatures) {
throw new JSONException("unsupported operation");
}
@Override
public void setCallback(Object object, BiFunction callback) {
throw new JSONException("unsupported operation");
}
@Override
public void setInt(Object object, int value) {
throw new JSONException("unsupported operation");
}
@Override
public void setLong(Object object, long value) {
throw new JSONException("unsupported operation");
}
@Override
public boolean remove(Object object) {
return false;
}
@Override
public JSONPath getParent() {
return null;
}
}
/**
* Represents the execution context for JSONPath operations
*/
static final class Context {
final JSONPath path;
final Context parent;
final JSONPathSegment current;
final JSONPathSegment next;
final long readerFeatures;
Object root;
Object value;
boolean eval;
Context(JSONPath path, Context parent, JSONPathSegment current, JSONPathSegment next, long readerFeatures) {
this.path = path;
this.current = current;
this.next = next;
this.parent = parent;
this.readerFeatures = readerFeatures;
}
}
/**
* Represents a sequence of values
*/
static class Sequence {
final List values;
public Sequence(List values) {
this.values = values;
}
}
/**
* Features that can be applied to JSONPath operations
*/
public enum Feature {
/**
* Always return results as a list
*/
AlwaysReturnList(1),
/**
* Return null on error instead of throwing exceptions
*/
NullOnError(1 << 1),
/**
* Keep null values in results
*/
KeepNullValue(1 << 2);
public final long mask;
Feature(long mask) {
this.mask = mask;
}
}
}