Skip to content

Commit f2e817a

Browse files
cursoragentsimbo1905
andcommitted
Add fluent API: JsonPath.parse(...).select(...)
Adds alternative API style for JsonPath queries: - JsonPath.parse(expr) returns a compiled JsonPath - .select(json) evaluates against a document - Compiled paths are reusable across multiple documents - Retains original JsonPath.query(expr, json) as convenience method Tests verify: - parse(...).select(...) returns same results as query(...) - Compiled paths can be reused on different documents - expression() accessor returns original path string To verify: mvn test -pl json-java21-jsonpath Co-authored-by: simbo1905 <simbo1905@60hertz.com>
1 parent 262dada commit f2e817a

File tree

2 files changed

+83
-12
lines changed

2 files changed

+83
-12
lines changed

json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
/// JsonPath query evaluator for JSON documents.
1111
/// Parses JsonPath expressions and evaluates them against JsonValue instances.
1212
///
13-
/// Usage example:
13+
/// Usage examples:
1414
/// ```java
15+
/// // Fluent API (preferred)
1516
/// JsonValue json = Json.parse(jsonString);
17+
/// List<JsonValue> results = JsonPath.parse("$.store.book[*].author").select(json);
18+
///
19+
/// // Static query API
1620
/// List<JsonValue> results = JsonPath.query("$.store.book[*].author", json);
1721
/// ```
1822
///
@@ -21,31 +25,61 @@ public final class JsonPath {
2125

2226
private static final Logger LOG = Logger.getLogger(JsonPath.class.getName());
2327

24-
private JsonPath() {
25-
// No instantiation
28+
private final JsonPathAst.Root ast;
29+
private final String pathExpression;
30+
31+
private JsonPath(String pathExpression, JsonPathAst.Root ast) {
32+
this.pathExpression = pathExpression;
33+
this.ast = ast;
2634
}
2735

28-
/// Evaluates a JsonPath expression against a JSON document.
36+
/// Parses a JsonPath expression and returns a compiled JsonPath for reuse.
2937
/// @param path the JsonPath expression
30-
/// @param json the JSON document to query
31-
/// @return a list of matching JsonValue instances (may be empty)
32-
/// @throws NullPointerException if path or json is null
38+
/// @return a compiled JsonPath that can be used to select from multiple documents
39+
/// @throws NullPointerException if path is null
3340
/// @throws JsonPathParseException if the path is invalid
34-
public static List<JsonValue> query(String path, JsonValue json) {
41+
public static JsonPath parse(String path) {
3542
Objects.requireNonNull(path, "path must not be null");
43+
LOG.fine(() -> "Parsing path: " + path);
44+
final var ast = JsonPathParser.parse(path);
45+
return new JsonPath(path, ast);
46+
}
47+
48+
/// Selects matching values from a JSON document.
49+
/// @param json the JSON document to query
50+
/// @return a list of matching JsonValue instances (may be empty)
51+
/// @throws NullPointerException if json is null
52+
public List<JsonValue> select(JsonValue json) {
3653
Objects.requireNonNull(json, "json must not be null");
54+
LOG.fine(() -> "Selecting from document with path: " + pathExpression);
55+
return evaluate(ast, json);
56+
}
3757

38-
LOG.fine(() -> "Querying path: " + path);
58+
/// Returns the original path expression.
59+
public String expression() {
60+
return pathExpression;
61+
}
3962

40-
final var ast = JsonPathParser.parse(path);
41-
return evaluate(ast, json);
63+
/// Returns the parsed AST.
64+
public JsonPathAst.Root ast() {
65+
return ast;
66+
}
67+
68+
/// Evaluates a JsonPath expression against a JSON document (convenience method).
69+
/// @param path the JsonPath expression
70+
/// @param json the JSON document to query
71+
/// @return a list of matching JsonValue instances (may be empty)
72+
/// @throws NullPointerException if path or json is null
73+
/// @throws JsonPathParseException if the path is invalid
74+
public static List<JsonValue> query(String path, JsonValue json) {
75+
return parse(path).select(json);
4276
}
4377

4478
/// Evaluates a pre-parsed JsonPath AST against a JSON document.
4579
/// @param ast the parsed JsonPath AST
4680
/// @param json the JSON document to query
4781
/// @return a list of matching JsonValue instances (may be empty)
48-
public static List<JsonValue> evaluate(JsonPathAst.Root ast, JsonValue json) {
82+
static List<JsonValue> evaluate(JsonPathAst.Root ast, JsonValue json) {
4983
Objects.requireNonNull(ast, "ast must not be null");
5084
Objects.requireNonNull(json, "json must not be null");
5185

json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,41 @@ void testFilterLessOrEqual() {
323323
final var results = JsonPath.query("$..book[?(@.price<=8.99)]", storeJson);
324324
assertThat(results).hasSize(2);
325325
}
326+
327+
// ========== Fluent API tests ==========
328+
329+
@Test
330+
void testFluentApiParseAndSelect() {
331+
LOG.info(() -> "TEST: testFluentApiParseAndSelect - JsonPath.parse(...).select(...)");
332+
final var matches = JsonPath.parse("$.store.book").select(storeJson);
333+
assertThat(matches).hasSize(1);
334+
assertThat(matches.getFirst()).isInstanceOf(JsonArray.class);
335+
final var bookArray = (JsonArray) matches.getFirst();
336+
assertThat(bookArray.elements()).hasSize(4); // 4 books in the array
337+
}
338+
339+
@Test
340+
void testFluentApiReusable() {
341+
LOG.info(() -> "TEST: testFluentApiReusable - compiled path can be reused");
342+
final var compiledPath = JsonPath.parse("$..price");
343+
344+
// Use on store doc
345+
final var storeResults = compiledPath.select(storeJson);
346+
assertThat(storeResults).hasSize(5); // 4 book prices + 1 bicycle price
347+
348+
// Use on a different doc
349+
final var simpleDoc = Json.parse("""
350+
{"item": {"price": 99.99}}
351+
""");
352+
final var simpleResults = compiledPath.select(simpleDoc);
353+
assertThat(simpleResults).hasSize(1);
354+
assertThat(((JsonNumber) simpleResults.getFirst()).toDouble()).isEqualTo(99.99);
355+
}
356+
357+
@Test
358+
void testFluentApiExpressionAccessor() {
359+
LOG.info(() -> "TEST: testFluentApiExpressionAccessor - expression() returns original path");
360+
final var path = JsonPath.parse("$.store.book[*].author");
361+
assertThat(path.expression()).isEqualTo("$.store.book[*].author");
362+
}
326363
}

0 commit comments

Comments
 (0)