JQuick-Path
JQuick-Path
Section titled “JQuick-Path”Module overview
Section titled “Module overview”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.
Features
Section titled “Features”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,JSegmentsandJSubscripts— no hand-written strings required. - String path expressions: expressions such as
$.store.books..[2]can be passed directly topath(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 rawcustomexpressions. - Complete filter expression engine: comparison, logic, arithmetic,
in, negation, regex, and nested function calls such as@.length(). - Multiple input sources: JSON strings,
Map, POJO orJSONObject. - Result handling:
JSONPathResultexposesgetRawData(),getAsList()andisList();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,@JSONFormatfor path-based field and result binding. - Extensible utilities:
JValueContextFactoryprovides context-based value resolution, andJTemplateEngineprovides template evaluation. - Apache-2.0 license: free for commercial and personal projects.
Use cases
Section titled “Use cases”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.
Quick start
Section titled “Quick start”Maven dependency
Section titled “Maven dependency”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.
Gradle dependency
Section titled “Gradle dependency”implementation 'io.github.paohaijiao:jquick-path:2.0.0'The README also provides documentation for expression functions: JEvaluator Function Reference.
Minimal example (single-value extraction)
Section titled “Minimal example (single-value extraction)”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()); }}Basic path expression syntax
Section titled “Basic path expression syntax”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 |
Field access .
Section titled “Field access .”- 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()); }}Array index access [index]
Section titled “Array index access [index]”- Purpose: locate an array element by index.
- Index rules: numeric indexes start at
0; as the README states forJSubscripts.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.
Iterate all array elements [*]
Section titled “Iterate all array elements [*]”- 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()); }}Extract multiple fields at once
Section titled “Extract multiple fields at once”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 typedJProjectionBuilder.
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.
Conditional filter expressions
Section titled “Conditional filter expressions”- Purpose: filter array elements with a
[?(...)]filter expression, where@denotes the current node. - Programmatic equivalent:
JSubscripts.filter(JPredicate<T>). - Predicates: use the
JPredicatepredicate factory, or pass a raw filter expression viaJPredicate.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()); }}Path syntax cheat sheet
Section titled “Path syntax cheat sheet”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.
Two usage modes
Section titled “Two usage modes”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()returnsJSONPathResult.
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.
Common extraction scenarios
Section titled “Common extraction scenarios”Extract properties from a plain object
Section titled “Extract properties from a plain object”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()); }}Extract a single element from an array
Section titled “Extract a single element from an array”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()); }}Extract all elements of an array
Section titled “Extract all elements of an array”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()); }}Filter and collect matching nodes
Section titled “Filter and collect matching nodes”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()); }}Multi-level nested JSON extraction
Section titled “Multi-level nested JSON extraction”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.
Mapping JSON to Java Beans
Section titled “Mapping JSON to Java Beans”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.
Map a single POJO directly
Section titled “Map a single POJO directly”// 普通书籍 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); }}Map array results to List<POJO>
Section titled “Map array results to List<POJO>”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.
Field aliases and ignoring missing fields
Section titled “Field aliases and ignoring missing fields”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.
Supported input sources
Section titled “Supported input sources”The README explicitly documents four input sources: JSON strings, Map, POJO (serialized first, then parsed), and JSONObject.
Parse from a JSON string
Section titled “Parse from a JSON string”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()); }}Parse directly from a Map
Section titled “Parse directly from a Map”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()); }}Null and exception policy
Section titled “Null and exception policy”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.
Missing nodes return null
Section titled “Missing nodes return null”This capability is not yet documented in the README.
Configure default return values
Section titled “Configure default return values”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.
Array index out-of-bounds behavior
Section titled “Array index out-of-bounds behavior”This capability is not yet documented in the README.
Advanced capabilities
Section titled “Advanced capabilities”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.
Dynamic path expression assembly
Section titled “Dynamic path expression assembly”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()); }}Custom type converters
Section titled “Custom type converters”This capability is not yet documented in the README.
Demo Gallery
Section titled “Demo Gallery”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.
1. Basic property queries
Section titled “1. Basic property queries”Root property access
Section titled “Root property access”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-pathRoot-level wildcard
Section titled “Root-level wildcard”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]Object property subscript
Section titled “Object property subscript”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()); }}martinAll array elements
Section titled “All array elements”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]2. Array subscripts and slicing
Section titled “2. Array subscripts and slicing”Positive subscript
Section titled “Positive subscript”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()); }}jsonWildcard subscript
Section titled “Wildcard subscript”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]Negative subscript
Section titled “Negative subscript”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()); }}pathArray slice
Section titled “Array slice”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]3. Recursive queries
Section titled “3. Recursive queries”Recursive property search
Section titled “Recursive property search”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]Recursive subscript
Section titled “Recursive subscript”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]4. Filtering
Section titled “4. Filtering”Equality filter
Section titled “Equality filter”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}]Greater-than filter
Section titled “Greater-than filter”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}]In membership check
Section titled “In membership check”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}]Logical combination
Section titled “Logical combination”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}]Negation
Section titled “Negation”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}]5. Expression operations
Section titled “5. Expression operations”Arithmetic subscript
Section titled “Arithmetic subscript”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()); }}pathNested function subscript
Section titled “Nested function subscript”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()); }}javaString path
Section titled “String path”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()); }}path6. Bean mapping
Section titled “6. Bean mapping”Single object mapping
Section titled “Single object mapping”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}List mapping
Section titled “List mapping”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}Mapping after sorting
Section titled “Mapping after sorting”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}7. Multi-source scenarios
Section titled “7. Multi-source scenarios”JSON string
Section titled “JSON string”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-pathMap object
Section titled “Map object”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-pathJSONObject object
Section titled “JSONObject object”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-pathComparison with JsonPath / Jackson / Gson
Section titled “Comparison with JsonPath / Jackson / Gson”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.
Known limitations
Section titled “Known limitations”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.
Repository, version & License
Section titled “Repository, version & License”- GitHub repository: https://github.com/paohaijiao/jquick-path
- Maven Central:
io.github.paohaijiao:jquick-path - Current version:
2.0.0(requires JDK 8 or later; JDK 17 recommended) - Issue tracking: GitHub Issues
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, softwaredistributed 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 andlimitations under the License.