Skip to content

JQuick-Path

JQuick-Path is a query language and runtime for extracting data from JSON documents. It plays the same role for JSON that XPath plays for XML: a single concise path expression can locate any node in a nested JSON structure.

JQuick-Path is implemented in pure Java and driven by a self-developed grammar parser, with no native dependencies; it behaves identically on Windows, Linux, macOS and domestic (Xinchuang) operating systems. Under the hood, the library provides a fluent, type-safe query builder JSONPathQueryBuilder and a complete expression engine: root/current-node selectors, property access, wildcards, recursive descent, array subscripts, slicing, filtering, arithmetic and logical expressions, plus support for mapping query results to Java Beans.

JQuick-Path does not depend on Spring and can be used standalone without any container; plain Java projects and Spring Boot projects use the same API.

All features documented in the README:

  • Fluent query builder: JSONPathQueryBuilder.from(...).document(...).limit(...).execute() chains the whole query.
  • Programmatic path construction: compose paths with JPath, JSegments and JSubscripts — no hand-written strings required.
  • String path expressions: expressions such as $.store.books..[2] can be passed directly to path(String).
  • Root and current-node selectors: $ (JRoot.ROOT) and @ (JRoot.CURRENT).
  • Segment selectors: property access, wildcard *, recursive descent .., subscript segments and recursive subscript segments.
  • Subscript selectors: numeric indexes, negative indexes, wildcards, object properties ['title'], slices [start:end:step], filters [?(...)] and arithmetic-expression subscripts.
  • Rich predicate library: eq, ne, gt, ge, lt, le, like, regex, startsWith, endsWith, contains, in, exists, notExists, and, or, not, plus raw custom expressions.
  • Complete filter expression engine: comparison, logic, arithmetic, in, negation, regex, and nested function calls such as @.length().
  • Multiple input sources: JSON strings, Map, POJO or JSONObject.
  • Result handling: JSONPathResult exposes getRawData(), getAsList() and isList(); as() / asList() map results to Java Beans.
  • Sorting and pagination: JSortBuilder.asc() / desc() for sorting, limit() / skip() for paging.
  • Grammar-driven parsing: self-developed grammar rules with a dedicated error listener produce clear syntax-error messages.
  • Cross-platform and Xinchuang compatibility: pure Java bytecode, no JNI; verified on Windows, Linux, macOS and on Kylin, UnionTech UOS, openEuler, Anolis and other environments with mainstream domestic JDKs.
  • Bounded traversal: recursive descent over untrusted JSON is constrained by limit() / skip(), preventing unbounded result growth and providing lightweight path-traversal protection.
  • Declarative annotations: @JSONPath, @JSONObject, @JSONArray, @JSONFilter, @JSONSort, @JSONFormat for path-based field and result binding.
  • Extensible utilities: JValueContextFactory provides context-based value resolution, and JTemplateEngine provides template evaluation.
  • Apache-2.0 license: free for commercial and personal projects.

Typical scenarios documented in the README:

  • Reading configuration: extract nested values from JSON config documents or configs converted from YAML.
  • API response processing: extract and restructure fields from third-party responses.
  • Data transformation: map JSON fragments directly to Java Beans via as() / asList().
  • Batch and pipeline jobs: filter, slice, sort and page large JSON collections before downstream processing.
  • Inspecting untrusted payloads: recursive descent is bounded by limit() / skip(), so a path expression cannot expand without limit over attacker-controlled JSON.

Requirements (from the README):

Item Requirement
JDK 8 or later (JDK 17 recommended)
Build tool Maven 3.6+ or Gradle 7+
Operating system Windows / Linux / macOS / Xinchuang operating systems
Dependencies Built-in lexical/syntax-analysis runtime, Hutool, Javelin Core, JQuick JSON — all pure Java, no native libraries
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-path</artifactId>
<version>2.0.0</version>
</dependency>

Use the latest version on Maven Central as authoritative; the English README uses the ${latest.version} placeholder.

implementation 'io.github.paohaijiao:jquick-path:2.0.0'

The README also provides documentation for expression functions: JEvaluator Function Reference.

The following example corresponds to the README example $.store.books[2].price; it extracts a single numeric value from nested JSON, and the output is 20.

public class QuickStartDemo {
public static void main(String[] args) {
// README 中的参考数据
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 从根节点开始:store -> books -> 下标 2 -> price
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books")
.segment(JSegments.subscript(JIndexSubscript.of(2)))
.property("price"))
.limit(10)
.execute();
// 输出:20
System.out.println(result.getRawData());
}
}

The README’s basic syntax table:

Expression Description
$ Root object
. or [ ] Child operator, used to access object properties
.. Recursive descent; searches all child elements (written as ... in the README syntax table and as .. in the examples)
* Wildcard; matches all object properties or array elements
[ ] Subscript operator, used for array indexing or filtering
[start:end:step] Array slice operation
?() Filter expression
@ Current node, used in filter expressions
  • Purpose: access an object’s properties. Both . and [ ] are child operators.
  • Programmatic equivalents: JPath.property(String) appends a property segment; JSegments.id(String) creates an identifier segment; JSubscripts.property(String) creates an object-property subscript of the form ['title'].
  • Parameter: the property name, a string, required.
public class FieldAccessDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":[]}}";
// 等价路径表达式:$.store.books
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books"))
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
  • Purpose: locate an array element by index.
  • Index rules: numeric indexes start at 0; as the README states for JSubscripts.index(int), negative indexes count from the end of the array.
  • Programmatic equivalents: JSubscripts.index(int), JIndexSubscript.of(int).
public class IndexDemo {
public static void main(String[] args) {
String jsonData = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}";
// 等价路径表达式:$.books[0]
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT).property("books")
.segment(JSubscriptSegment.of(JSubscripts.index(0))))
.limit(10)
.execute();
// 输出索引为 0 的书籍对象(Book 1)
System.out.println(result.getRawData());
}
}

The README also gives a negative-index example, $.books[-2] (specified via JSubscripts.expr("-2")), which outputs the corresponding element counted from the end.

  • Purpose: the wildcard matches all properties of an object or all elements of an array.
  • Programmatic equivalents: JSubscripts.wildcard(), property("*"), JSegments.wildcard().
public class WildcardDemo {
public static void main(String[] args) {
String jsonData = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}";
// 等价路径表达式:$.books[*]
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT).property("books")
.segment(JSubscriptSegment.of(JSubscripts.wildcard())))
.limit(10)
.execute();
// 输出 books 数组中的全部书籍对象
System.out.println(result.getRawData());
}
}

The README’s only public descriptions related to this topic are:

  • $.*: the wildcard; gets all properties under the root object (or current object); see the example above.
  • The entry method JSONPathQueryBuilder.select(Class<T>): creates a typed JProjectionBuilder.

The README provides neither syntax documentation nor code examples for specifying multiple named fields at once.

This capability is not yet documented in the README.

  • Purpose: filter array elements with a [?(...)] filter expression, where @ denotes the current node.
  • Programmatic equivalent: JSubscripts.filter(JPredicate<T>).
  • Predicates: use the JPredicate predicate factory, or pass a raw filter expression via JPredicate.custom(String).

Predicate factory capabilities documented in the README:

Predicate / method Purpose
eq / ne Equal to / not equal to
gt / ge / lt / le Greater than / greater than or equal / less than / less than or equal
like(prop, pattern) Wildcard matching (*, ?)
regex(prop, regex) Regular-expression matching
startsWith / endsWith / contains Common string matching operations implemented on top of regex
in(prop, values...) Membership test
exists / notExists Whether a property exists
and / or / not Logical composition
custom(String) Raw JSONPath filter expression
of(prop, operator, value) Generic predicate with an explicitly specified operator
public class FilterSyntaxDemo {
public static void main(String[] args) {
String jsonData = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}";
// 等价路径表达式:$.books[?(@.title == 'Book 1')]
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT).property("books")
.segment(JSubscriptSegment.of(
JSubscripts.filter(JPredicate.eq("title", "Book 1")))))
.limit(10)
.execute();
// 输出符合条件的书籍对象(Book 1)
System.out.println(result.getRawData());
}
}

Combining the README syntax table with its 33 examples, the forms that appear in path expressions are:

Form Meaning README example
$ Root object $.store.books
@ Current node @.store.books
.field Property access $.store.books[2].price
['field'] Object-property subscript $.extract['title']
[index] Numeric index $.books[0]
[-index] Negative index (counted from the end) $.books[-2]
[*] All array elements $.books[*]
.* All object properties $.*
..field Recursive-descent property match $.store.books..price
..* Recursive wildcard Generated by JSegments.recursiveWildcard()
..[index] Recursive subscript $.store.books..[2]
[start:end] Slice JSubscripts.slice(Integer, Integer)
[start:end:step] Slice with step $.books[0:1:2]
[?(...)] Filter expression $.books[?(@.price>15)]
[expr] Arithmetic / function expression subscript $.books[1+1], $.books[(@.length())-1]

Operators and forms that appear in the README filter expressions: arithmetic +, -, *, /, %; comparison >, >=, <, <=, ==, !=; logical &&, ||, !; membership tests in ('a','b'); negation; regex; and nested function calls such as @.length(). The available functions are defined by the JEvaluator Function Reference linked from the README.

Ad-hoc parsing: pass the path string directly (simple cases)

Section titled “Ad-hoc parsing: pass the path string directly (simple cases)”
  • Purpose: pass the path expression string directly to path(String) without building a path object first; suited to one-off, ad-hoc queries.
  • Parameter: the path expression string, required.
  • Return value: execute() returns JSONPathResult.
public class DirectPathDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 直接使用字符串路径表达式
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.path("$.store.books..[2]")
.limit(10)
.execute();
// 输出 books 数组中索引为 2 的书籍对象(Book 3)
System.out.println(result.getRawData());
}
}

In the README’s core API reference, the entry builder JSONPathQueryBuilder has the following methods:

Method Purpose
from(Object) Create a query from a POJO (serialized first, then parsed)
from(String) Create a query from a JSON string
from(Map) Create a query from a Map
from(JSONObject) Create a query from a JSONObject
root(JSubscripts) Create a query starting from a root subscript
filter(Class<T>) Create a typed JFilterBuilder<T>
of(JPredicate<T>) / of(String) Build a JFilterExpression from a predicate or raw expression
select(Class<T>) Create a typed JProjectionBuilder<T>

The fluent query JSONPathQuery<T> has the following methods:

Method Purpose
document(JPath) Set a programmatically constructed path
path(String) Set the path as a string expression
limit(int) Limit the number of returned elements
skip(int) Skip the first N elements (used together with limit)
sort(JSortBuilder<T>) Sort a list result (must be used with asList)
as(Class<T>) Map a single result to a Java Bean
asList(Class<T>) Map a list result to Java Beans
execute() Execute the query and return a JSONPathResult

JSONPathResult has the following methods:

Method Purpose Return type
getRawData() Get the raw result object Object
getAsList() Get the result as a List (a single value is wrapped) List
isList() Check whether the result is a list boolean

Root symbols JRoot:

Constant Symbol Purpose
JRoot.ROOT $ Root document
JRoot.CURRENT @ Current node

Precompiled JQuickPath object (frequent repeated queries, performance)

Section titled “Precompiled JQuickPath object (frequent repeated queries, performance)”

This capability is not yet documented in the README.

The README provides programmatic path construction via JPath, and a constructed path can be rendered to a JSONPath string with toJSONPathExpression(); however, the README does not use the concept of “precompilation”, nor does it describe how a precompiled object is reused or its performance characteristics, so this document does not elaborate.

The following example corresponds to the README example $.extract['title']; it outputs the value of the title property of the extract object.

public class PropertyDemo {
public static void main(String[] args) {
String jsonData = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}],"
+ "\"extract\":{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}}";
// 等价路径表达式:$.extract['title']
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT).property("extract")
.segment(JSubscriptSegment.of(JSubscripts.property("title"))))
.limit(10)
.execute();
// 输出:Book 3
System.out.println(result.getRawData());
}
}

The following example corresponds to the README example $.store.books[2]; it outputs the element at index 2 (Book 3).

public class ArrayElementDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 等价路径表达式:$.store.books[2]
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books")
.segment(JSegments.subscript(JIndexSubscript.of(2))))
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}

The following example corresponds to the README example $.store.books.*; it outputs all book objects in the array.

public class AllElementsDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 等价路径表达式:$.store.books.*
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books").property("*"))
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}

The following example corresponds to the README example $.books[?(@.price>15)]; it passes a raw filter expression via JPredicate.custom(String) and outputs the book objects whose price is greater than 15.

public class ConditionDemo {
public static void main(String[] args) {
String jsonData = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}";
// 等价路径表达式:$.books[?(@.price>15)]
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT).property("books")
.segment(JSubscriptSegment.of(
JSubscripts.filter(JPredicate.custom("@.price>15")))))
.limit(10)
.execute();
// 输出符合条件的书籍对象(Book 3)
System.out.println(result.getRawData());
}
}

The following example corresponds to the README example $.store.books..price; it uses recursive descent to search for every price value under the books array, and the output is [10, 15, 20].

public class NestedDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 等价路径表达式:$.store.books..price
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books")
.segment(JSegments.recursiveId("price")))
.limit(10)
.execute();
// 输出:[10, 15, 20]
System.out.println(result.getRawData());
}
}

Batch-extract multiple results with multiple paths

Section titled “Batch-extract multiple results with multiple paths”

This capability is not yet documented in the README.

The README documents two terminal methods on the fluent query for mapping:

  • as(Class<T>): map a single result to a Java Bean.
  • asList(Class<T>): map a list result to Java Beans.
// 普通书籍 POJO,字段对应参考 JSON 中的属性
public class Book {
private String title;
private String author;
private Integer price;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "Book{title='" + title + "', author='" + author + "', price=" + price + "}";
}
}
public class BeanMappingDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// README API:as(Class<T>) —— 将单个结果映射为 Java Bean
Book book = JSONPathQueryBuilder.from(jsonData)
.path("$.store.books[2]")
.as(Book.class);
// 输出:Book{title='Book 3', author='Author 3', price=20}
System.out.println(book);
}
}
public class BeanListMappingDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// README API:asList(Class<T>) —— 将列表结果映射为 Java Bean
List<Book> books = JSONPathQueryBuilder.from(jsonData)
.path("$.store.books[*]")
.asList(Book.class);
books.forEach(System.out::println);
}
}

The README also documents sort(JSortBuilder<T>) for sorting list results (must be used with asList); the sort builder offers JSortBuilder.asc() / desc(), but the README gives no sorting code example.

The README’s feature list includes declarative annotations such as @JSONPath, @JSONObject, @JSONArray, @JSONFilter, @JSONSort and @JSONFormat, but it does not describe how to configure field-alias mapping or how missing fields are ignored.

This capability is not yet documented in the README.

The README explicitly documents four input sources: JSON strings, Map, POJO (serialized first, then parsed), and JSONObject.

public class StringSourceDemo {
public static void main(String[] args) {
String jsonString = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\"},"
+ "{\"title\":\"Book 2\"}]}}";
// 入口方法:from(String)
JSONPathResult result = JSONPathQueryBuilder.from(jsonString)
.path("$.store.books[0].title")
.limit(10)
.execute();
// 输出:Book 1
System.out.println(result.getRawData());
}
}
public class MapSourceDemo {
public static void main(String[] args) {
Map<String, Object> root = new HashMap<String, Object>();
Map<String, Object> store = new HashMap<String, Object>();
store.put("name", "JQuick");
root.put("store", store);
// 入口方法:from(Map)
JSONPathResult result = JSONPathQueryBuilder.from(root)
.path("$.store.name")
.limit(10)
.execute();
// 输出:JQuick
System.out.println(result.getRawData());
}
}

Continue querying from an intermediate node

Section titled “Continue querying from an intermediate node”

The README states that @ (JRoot.CURRENT) denotes the current node and provides the entry method from(JSONObject) to start a query from a JSONObject. The following example corresponds to the README’s “current-node expression” example; the path starts from the current node and visits store, then books.

public class NodeSourceDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
// 等价路径表达式:@.store.books
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.CURRENT)
.property("store").property("books"))
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}

The README only covers error diagnostics at the syntax level: self-developed grammar rules with a dedicated error listener provide clear syntax-error messages. The following runtime policies concerning missing nodes, default values, exception mode and array out-of-bounds access are not covered by the README.

This capability is not yet documented in the README.

This capability is not yet documented in the README.

Enable exception mode (throw on missing nodes)

Section titled “Enable exception mode (throw on missing nodes)”

This capability is not yet documented in the README.

This capability is not yet documented in the README.

Raw node metadata (node type, parent node)

Section titled “Raw node metadata (node type, parent node)”

This capability is not yet documented in the README.

The programmatic path construction documented in the README is exactly what you use to compose paths dynamically. The path builders JPath / JPaths have the following methods:

Method Purpose
JPath.fromRoot(JRoot) Start building a path from the root node ($) or the current node (@)
JPath.documentRoot() / JPaths.documentPath() Start building a path from the root node
JPath.currentRoot() / JPaths.currentPath() Start building a path from the current node
segment(JSegment) / segments(JSegment...) Append one or more segments
property(String) Append a property identifier segment
wildcard() Append a wildcard segment
index(int) Append a subscript segment
filter(JPredicate<?>) Append a filter subscript segment
recursiveProperty(String) Append a recursive identifier segment
recursiveWildcard() Append a recursive wildcard segment
toJSONPathExpression() Render the path as a JSONPath string

Segment factory JSegments:

Method Purpose
id(String) Identifier segment (.property)
wildcard() Wildcard identifier segment (.*)
subscript(JSubscript) Subscript segment ([...])
recursiveId(String) Recursive identifier segment (..property)
recursiveWildcard() Recursive wildcard segment (..*)
recursiveSubscript(JSubscript) Recursive subscript segment (..[...])

Subscript factory JSubscripts:

Method Purpose
index(int) Numeric index; negative values count from the end
wildcard() All elements ([*])
property(String) Object property (['title'])
slice(Integer, Integer) Start/end slice ([0:1])
slice(Integer, Integer, Integer) Slice with step ([0:1:2])
filter(JPredicate<T>) Filter expression ([?(...)])
expr(String) Raw arithmetic / function expression subscript ([0*1])

With these APIs you can assemble a path segment by segment at runtime according to business conditions — for example, the README’s combination of “property segment + subscript segment + property segment”:

public class DynamicPathDemo {
public static void main(String[] args) {
String jsonData = "{\"store\":{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}}";
int index = 2;
// 等价路径表达式:$.store.books[2].price
JSONPathResult result = JSONPathQueryBuilder.from(jsonData)
.document(JPath.fromRoot(JRoot.ROOT)
.property("store").property("books")
.segment(JSegments.subscript(JIndexSubscript.of(index)))
.property("price"))
.limit(10)
.execute();
// 输出:20
System.out.println(result.getRawData());
}
}

This capability is not yet documented in the README.

This gallery walks through JQuick-Path from the simplest property access to advanced multi-source scenarios. Each demo contains four blocks: the equivalent JQuick-Path expression, the input JSON, a complete runnable Java snippet, and the execution output.

Expression $.name: access the top-level property name from the root.

{ "name": "jquick-path", "version": "1.0.0" }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo1 {
public static void main(String[] args) {
String json = "{\"name\":\"jquick-path\",\"version\":\"1.0.0\"}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.name")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
jquick-path

Expression $.*: read all top-level properties as a list.

{ "name": "jquick-path", "version": "1.0.0" }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo2 {
public static void main(String[] args) {
String json = "{\"name\":\"jquick-path\",\"version\":\"1.0.0\"}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.*")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[jquick-path, 1.0.0]

Expression $['author']['name']: access nested object properties via [].

{ "author": { "name": "martin", "city": "hangzhou" } }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo3 {
public static void main(String[] args) {
String json = "{\"author\":{\"name\":\"martin\",\"city\":\"hangzhou\"}}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$['author']['name']")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
martin

Expression $.tags[*]: return every element of the array.

{ "tags": ["json", "path", "query"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo4 {
public static void main(String[] args) {
String json = "{\"tags\":[\"json\",\"path\",\"query\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.tags[*]")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[json, path, query]

Expression $.books[1]: access the element at the given zero-based index.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo5 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[1]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
json

Expression $.books[*]: equivalent to reading every element of the array.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo6 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[*]")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[java, json, path]

Expression $.books[-1]: index from the end of the array; -1 is the last element.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo7 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[-1]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
path

Expression $.books[0:2]: a half-open range [start, end); end is exclusive.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo8 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[0:2]")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[java, json]

Expression $..price: search every level of the document for properties named price.

{
"store": {
"books": [{ "price": 10 }, { "price": 20 }],
"bicycle": { "price": 100 }
}
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo9 {
public static void main(String[] args) {
String json = "{\"store\":{\"books\":[{\"price\":10},{\"price\":20}],\"bicycle\":{\"price\":100}}}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$..price")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[10, 20, 100]

Expression $..[2]: find the element at index 2 inside every array in the document.

{
"tags": ["a", "b", "c"],
"store": { "books": ["java", "json", "path"] }
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo10 {
public static void main(String[] args) {
String json = "{\"tags\":[\"a\",\"b\",\"c\"],\"store\":{\"books\":[\"java\",\"json\",\"path\"]}}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$..[2]")
.limit(10)
.execute();
System.out.println(result.getAsList());
}
}
[c, path]

Expression $.books[?(@.name=='Book 1')]: keep array elements whose name equals Book 1.

{
"books": [
{ "name": "Book 1", "price": 10 },
{ "name": "Book 2", "price": 20 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo11 {
public static void main(String[] args) {
String json = "{\"books\":[{\"name\":\"Book 1\",\"price\":10},{\"name\":\"Book 2\",\"price\":20}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[?(@.name=='Book 1')]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
[{"name":"Book 1","price":10}]

Expression $.books[?(@.price > 15)]: keep elements whose price is greater than 15.

{
"books": [
{ "name": "Book 1", "price": 10 },
{ "name": "Book 2", "price": 20 },
{ "name": "Book 3", "price": 30 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo12 {
public static void main(String[] args) {
String json = "{\"books\":[{\"name\":\"Book 1\",\"price\":10},{\"name\":\"Book 2\",\"price\":20},{\"name\":\"Book 3\",\"price\":30}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[?(@.price > 15)]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
[{"name":"Book 2","price":20},{"name":"Book 3","price":30}]

Expression $.books[?(@.name in ['Book 1','Book 3'])]: keep elements whose name is in the given list.

{
"books": [
{ "name": "Book 1", "price": 10 },
{ "name": "Book 2", "price": 20 },
{ "name": "Book 3", "price": 30 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo13 {
public static void main(String[] args) {
String json = "{\"books\":[{\"name\":\"Book 1\",\"price\":10},{\"name\":\"Book 2\",\"price\":20},{\"name\":\"Book 3\",\"price\":30}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[?(@.name in ['Book 1','Book 3'])]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
[{"name":"Book 1","price":10},{"name":"Book 3","price":30}]

Expression $.books[?(@.price > 10 && @.price < 30)]: combine conditions with &&.

{
"books": [
{ "name": "Book 1", "price": 10 },
{ "name": "Book 2", "price": 20 },
{ "name": "Book 3", "price": 30 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo14 {
public static void main(String[] args) {
String json = "{\"books\":[{\"name\":\"Book 1\",\"price\":10},{\"name\":\"Book 2\",\"price\":20},{\"name\":\"Book 3\",\"price\":30}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[?(@.price > 10 && @.price < 30)]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
[{"name":"Book 2","price":20}]

Expression $.books[?(!(@.price > 15))]: negate the inner condition.

{
"books": [
{ "name": "Book 1", "price": 10 },
{ "name": "Book 2", "price": 20 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo15 {
public static void main(String[] args) {
String json = "{\"books\":[{\"name\":\"Book 1\",\"price\":10},{\"name\":\"Book 2\",\"price\":20}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[?(!(@.price > 15))]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
[{"name":"Book 1","price":10}]

Expression $.books[1+1]: the index is computed from an arithmetic expression.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo16 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[1+1]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
path

Expression $.books[size($.tags)]: the index is the result of a nested function call.

{ "tags": ["a", "b", "c"], "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class Demo17 {
public static void main(String[] args) {
String json = "{\"tags\":[\"a\",\"b\",\"c\"],\"books\":[\"java\",\"json\",\"path\"]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[size($.tags)]")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
java

Expression $.books[2]: build the query with the fluent JPath API instead of a literal string.

{ "books": ["java", "json", "path"] }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
import com.github.paohaijiao.selector.root.JPath;
import com.github.paohaijiao.selector.root.JRoot;
import com.github.paohaijiao.selector.segment.JSegments;
import com.github.paohaijiao.selector.subscript.JIndexSubscript;
public class Demo18 {
public static void main(String[] args) {
String json = "{\"books\":[\"java\",\"json\",\"path\"]}";
JPath path = JPath.fromRoot(JRoot.ROOT)
.property("books")
.subscript(JIndexSubscript.of(2));
JSONPathResult result = JSONPathQueryBuilder.from(json)
.document(path)
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
path

Expression $.books[2]: the result is a JSONObject; map it to a Java Bean with toBean(Class).

{
"books": [
{ "title": "Book 1", "author": "Author 1", "price": 10 },
{ "title": "Book 2", "author": "Author 2", "price": 15 },
{ "title": "Book 3", "author": "Author 3", "price": 20 }
]
}
public class Book {
private String title;
private String author;
private Integer price;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public Integer getPrice() { return price; }
public void setPrice(Integer price) { this.price = price; }
@Override
public String toString() {
return "Book{title='" + title + "', author='" + author + "', price=" + price + "}";
}
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONObject;
import com.github.paohaijiao.model.JSONPathResult;
public class BeanSingleDemo {
public static void main(String[] args) {
String json = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15},"
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[2]")
.execute();
// The single result is a JSONObject; call toBean to map fields
Book book = ((JSONObject) result.getRawData()).toBean(Book.class);
System.out.println(book);
}
}
Book{title='Book 3', author='Author 3', price=20}

Expression $.books[*]: call toBean(Class) on each item and assemble a List<Book>.

{
"books": [
{ "title": "Book 1", "author": "Author 1", "price": 10 },
{ "title": "Book 2", "author": "Author 2", "price": 15 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONObject;
import com.github.paohaijiao.model.JSONPathResult;
import java.util.ArrayList;
import java.util.List;
public class BeanListDemo {
public static void main(String[] args) {
String json = "{\"books\":["
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15}]}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.books[*]")
.execute();
List<Book> books = new ArrayList<Book>();
for (Object item : result.getAsList()) {
books.add(((JSONObject) item).toBean(Book.class));
}
books.forEach(System.out::println);
}
}
Book{title='Book 1', author='Author 1', price=10}
Book{title='Book 2', author='Author 2', price=15}

Expression $.books: asList(Class) together with JSortBuilder.asc("price") sorts the result inside the query pipeline and maps it to List<Book>.

{
"books": [
{ "title": "Book 3", "author": "Author 3", "price": 20 },
{ "title": "Book 1", "author": "Author 1", "price": 10 },
{ "title": "Book 2", "author": "Author 2", "price": 15 }
]
}
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
import com.github.paohaijiao.query.impl.JSortBuilder;
import java.util.List;
public class BeanSortDemo {
public static void main(String[] args) {
String json = "{\"books\":["
+ "{\"title\":\"Book 3\",\"author\":\"Author 3\",\"price\":20},"
+ "{\"title\":\"Book 1\",\"author\":\"Author 1\",\"price\":10},"
+ "{\"title\":\"Book 2\",\"author\":\"Author 2\",\"price\":15}]}";
JSONPathResult result = JSONPathQueryBuilder.<Book>from(json)
.path("$.books")
.sort(new JSortBuilder<Book>().asc("price"))
.asList(Book.class)
.execute();
List<Book> books = (List<Book>) result.getRawData();
books.forEach(System.out::println);
}
}
Book{title='Book 1', author='Author 1', price=10}
Book{title='Book 2', author='Author 2', price=15}
Book{title='Book 3', author='Author 3', price=20}

Use JSONPathQueryBuilder.from(String) to query a JSON string directly.

{ "name": "jquick-path" }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
public class SourceStringDemo {
public static void main(String[] args) {
String json = "{\"name\":\"jquick-path\"}";
JSONPathResult result = JSONPathQueryBuilder.from(json)
.path("$.name")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
jquick-path

Use JSONPathQueryBuilder.from(Map) to query a Map object directly.

{ "name": "jquick-path", "version": "1.0.0" }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONPathResult;
import java.util.HashMap;
import java.util.Map;
public class SourceMapDemo {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "jquick-path");
map.put("version", "1.0.0");
JSONPathResult result = JSONPathQueryBuilder.from(map)
.path("$.name")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
jquick-path

Use JSONPathQueryBuilder.from(JSONObject) to query a JSONObject directly.

{ "name": "jquick-path", "version": "1.0.0" }
import com.github.paohaijiao.builder.JSONPathQueryBuilder;
import com.github.paohaijiao.model.JSONObject;
import com.github.paohaijiao.model.JSONPathResult;
public class SourceObjectDemo {
public static void main(String[] args) {
JSONObject obj = new JSONObject();
obj.put("name", "jquick-path");
obj.put("version", "1.0.0");
JSONPathResult result = JSONPathQueryBuilder.from(obj)
.path("$.name")
.limit(10)
.execute();
System.out.println(result.getRawData());
}
}
jquick-path

The README provides no direct comparison between JQuick-Path and JsonPath, Jackson or Gson; it only lists pure-Java dependencies such as a built-in lexical/syntax-analysis runtime, Hutool, Javelin Core and JQuick JSON in its dependency list.

This comparison is not yet documented in the README.

This section is not yet documented in the README.

The README has no “known limitations” section. One design constraint it does state explicitly is:

  • Bounded traversal: recursive descent over untrusted JSON is constrained by limit() / skip(), preventing unbounded result growth and resource exhaustion.

Beyond this design constraint, the README lists no other limitations.

JQuick-Path is released under the Apache License, Version 2.0.

Copyright (c) [2025-2099] Martin ([email protected])
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.