Skip to content

Commit 262dada

Browse files
cursoragentsimbo1905
andcommitted
Add json-java21-jsonpath module implementing JsonPath query engine
Implements JsonPath query language for the java.util.json backport based on Stefan Goessner's specification (https://goessner.net/articles/JsonPath/). Features: - Parses JsonPath expressions to AST (sealed interface + records) - Evaluates AST against JsonValue documents from core library - No external dependencies (java.base only) - Java 21 functional style (immutable records, pattern matching) - Pure TDD development with 76 comprehensive tests Supported operators: - Root ($), property access (.name, ['name']) - Array index ([n], [-1]), slice ([:2], [0:10:2]) - Wildcard ([*], .*), recursive descent (..) - Union ([0,1], ['a','b']) - Filter expressions ([?(@.isbn)], [?(@.price<10)]) - Script expressions ([(@.length-1)]) All examples from Goessner article implemented as tests. To verify: mvn test -pl json-java21-jsonpath -Djava.util.logging.ConsoleHandler.level=INFO Co-authored-by: simbo1905 <simbo1905@60hertz.com>
1 parent 4b60358 commit 262dada

File tree

12 files changed

+2448
-0
lines changed

12 files changed

+2448
-0
lines changed

json-java21-jsonpath/AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# json-java21-jsonpath Module AGENTS.md
2+
3+
## Purpose
4+
This module implements a JsonPath query engine for the java.util.json Java 21 backport. It parses JsonPath expressions into an AST and evaluates them against JSON documents.
5+
6+
## Specification
7+
Based on Stefan Goessner's JSONPath specification:
8+
https://goessner.net/articles/JsonPath/
9+
10+
## Module Structure
11+
12+
### Main Classes
13+
- `JsonPath`: Public API facade with `query()` method
14+
- `JsonPathAst`: Sealed interface hierarchy defining the AST
15+
- `JsonPathParser`: Recursive descent parser
16+
- `JsonPathParseException`: Parse error exception
17+
18+
### Test Classes
19+
- `JsonPathLoggingConfig`: JUL test configuration base class
20+
- `JsonPathAstTest`: Unit tests for AST records
21+
- `JsonPathParserTest`: Unit tests for parser
22+
- `JsonPathGoessnerTest`: Integration tests based on Goessner article examples
23+
24+
## Development Guidelines
25+
26+
### Adding New Operators
27+
1. Add new record type to `JsonPathAst.Segment` sealed interface
28+
2. Update `JsonPathParser` to parse the new syntax
29+
3. Add parser tests in `JsonPathParserTest`
30+
4. Implement evaluation in `JsonPath.evaluateSegments()`
31+
5. Add integration tests in `JsonPathGoessnerTest`
32+
33+
### Testing
34+
```bash
35+
# Run all tests
36+
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jsonpath -Djava.util.logging.ConsoleHandler.level=INFO
37+
38+
# Run specific test class with debug logging
39+
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jsonpath -Dtest=JsonPathGoessnerTest -Djava.util.logging.ConsoleHandler.level=FINE
40+
41+
# Run single test method
42+
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jsonpath -Dtest=JsonPathGoessnerTest#testAuthorsOfAllBooks -Djava.util.logging.ConsoleHandler.level=FINEST
43+
```
44+
45+
## Design Principles
46+
47+
1. **No external dependencies**: Only java.base is allowed
48+
2. **Pure TDD**: Tests first, then implementation
49+
3. **Functional style**: Immutable records, pure evaluation functions
50+
4. **Java 21 features**: Records, sealed interfaces, pattern matching with switch
51+
5. **Defensive copies**: All collections in records are copied for immutability
52+
53+
## Known Limitations
54+
55+
1. **Script expressions**: Only `@.length-1` pattern is supported
56+
2. **No general expression evaluation**: Complex scripts are not supported
57+
3. **Stack-based recursion**: May overflow on very deep documents
58+
59+
## API Usage
60+
61+
```java
62+
import jdk.sandbox.java.util.json.*;
63+
import json.java21.jsonpath.JsonPath;
64+
65+
JsonValue json = Json.parse(jsonString);
66+
List<JsonValue> results = JsonPath.query("$.store.book[*].author", json);
67+
```
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# JsonPath Module Architecture
2+
3+
## Overview
4+
5+
This module implements a JsonPath query engine for the java.util.json Java 21 backport. It parses JsonPath expressions into an AST (Abstract Syntax Tree) and evaluates them against JSON documents parsed by the core library.
6+
7+
**Key Design Principles:**
8+
- **No external dependencies**: Only uses java.base
9+
- **Pure TDD development**: Tests define expected behavior before implementation
10+
- **Functional programming style**: Immutable data structures, pure functions
11+
- **Java 21 features**: Records, sealed interfaces, pattern matching
12+
13+
## JsonPath Specification
14+
15+
Based on the original JSONPath specification by Stefan Goessner:
16+
https://goessner.net/articles/JsonPath/
17+
18+
### Supported Operators
19+
20+
| Operator | Description | Example |
21+
|----------|-------------|---------|
22+
| `$` | Root element | `$` |
23+
| `.property` | Child property access | `$.store` |
24+
| `['property']` | Bracket notation property | `$['store']` |
25+
| `[n]` | Array index (supports negative) | `$.book[0]`, `$.book[-1]` |
26+
| `[start:end:step]` | Array slice | `$.book[:2]`, `$.book[::2]` |
27+
| `[*]` or `.*` | Wildcard (all children) | `$.store.*`, `$.book[*]` |
28+
| `..` | Recursive descent | `$..author` |
29+
| `[n,m]` | Union of indices | `$.book[0,1]` |
30+
| `['a','b']` | Union of properties | `$.store['book','bicycle']` |
31+
| `[?(@.prop)]` | Filter by existence | `$..book[?(@.isbn)]` |
32+
| `[?(@.prop op value)]` | Filter by comparison | `$..book[?(@.price<10)]` |
33+
| `[(@.length-1)]` | Script expression | `$..book[(@.length-1)]` |
34+
35+
### Comparison Operators
36+
37+
| Operator | Description |
38+
|----------|-------------|
39+
| `==` | Equal |
40+
| `!=` | Not equal |
41+
| `<` | Less than |
42+
| `<=` | Less than or equal |
43+
| `>` | Greater than |
44+
| `>=` | Greater than or equal |
45+
46+
## Module Structure
47+
48+
```
49+
json-java21-jsonpath/
50+
├── src/main/java/json/java21/jsonpath/
51+
│ ├── JsonPath.java # Public API facade
52+
│ ├── JsonPathAst.java # AST definition (sealed interface + records)
53+
│ ├── JsonPathParser.java # Recursive descent parser
54+
│ └── JsonPathParseException.java # Parse error exception
55+
└── src/test/java/json/java21/jsonpath/
56+
├── JsonPathLoggingConfig.java # JUL test configuration
57+
├── JsonPathAstTest.java # AST unit tests
58+
├── JsonPathParserTest.java # Parser unit tests
59+
└── JsonPathGoessnerTest.java # Goessner article examples
60+
```
61+
62+
## AST Design
63+
64+
The AST uses a sealed interface hierarchy with record implementations:
65+
66+
```mermaid
67+
classDiagram
68+
class JsonPathAst {
69+
<<sealed interface>>
70+
}
71+
class Root {
72+
<<record>>
73+
+segments: List~Segment~
74+
}
75+
class Segment {
76+
<<sealed interface>>
77+
}
78+
class PropertyAccess {
79+
<<record>>
80+
+name: String
81+
}
82+
class ArrayIndex {
83+
<<record>>
84+
+index: int
85+
}
86+
class ArraySlice {
87+
<<record>>
88+
+start: Integer
89+
+end: Integer
90+
+step: Integer
91+
}
92+
class Wildcard {
93+
<<record>>
94+
}
95+
class RecursiveDescent {
96+
<<record>>
97+
+target: Segment
98+
}
99+
class Filter {
100+
<<record>>
101+
+expression: FilterExpression
102+
}
103+
class Union {
104+
<<record>>
105+
+selectors: List~Segment~
106+
}
107+
class ScriptExpression {
108+
<<record>>
109+
+script: String
110+
}
111+
112+
JsonPathAst <|-- Root
113+
Segment <|-- PropertyAccess
114+
Segment <|-- ArrayIndex
115+
Segment <|-- ArraySlice
116+
Segment <|-- Wildcard
117+
Segment <|-- RecursiveDescent
118+
Segment <|-- Filter
119+
Segment <|-- Union
120+
Segment <|-- ScriptExpression
121+
```
122+
123+
## Evaluation Flow
124+
125+
```mermaid
126+
flowchart TD
127+
A[JsonPath.query] --> B[JsonPathParser.parse]
128+
B --> C[AST Root]
129+
C --> D[JsonPath.evaluate]
130+
D --> E[evaluateSegments]
131+
E --> F{Segment Type}
132+
F -->|PropertyAccess| G[evaluatePropertyAccess]
133+
F -->|ArrayIndex| H[evaluateArrayIndex]
134+
F -->|Wildcard| I[evaluateWildcard]
135+
F -->|RecursiveDescent| J[evaluateRecursiveDescent]
136+
F -->|Filter| K[evaluateFilter]
137+
F -->|ArraySlice| L[evaluateArraySlice]
138+
F -->|Union| M[evaluateUnion]
139+
F -->|ScriptExpression| N[evaluateScriptExpression]
140+
G --> O[Recurse with next segment]
141+
H --> O
142+
I --> O
143+
J --> O
144+
K --> O
145+
L --> O
146+
M --> O
147+
N --> O
148+
O --> P{More segments?}
149+
P -->|Yes| E
150+
P -->|No| Q[Add to results]
151+
```
152+
153+
## Usage Example
154+
155+
```java
156+
import jdk.sandbox.java.util.json.*;
157+
import json.java21.jsonpath.JsonPath;
158+
159+
// Parse JSON
160+
JsonValue json = Json.parse("""
161+
{
162+
"store": {
163+
"book": [
164+
{"title": "Book 1", "price": 8.95},
165+
{"title": "Book 2", "price": 12.99}
166+
]
167+
}
168+
}
169+
""");
170+
171+
// Query authors of all books
172+
List<JsonValue> titles = JsonPath.query("$.store.book[*].title", json);
173+
174+
// Query books cheaper than 10
175+
List<JsonValue> cheapBooks = JsonPath.query("$.store.book[?(@.price<10)]", json);
176+
177+
// Query all prices recursively
178+
List<JsonValue> prices = JsonPath.query("$..price", json);
179+
```
180+
181+
## Testing
182+
183+
Run all JsonPath tests:
184+
185+
```bash
186+
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jsonpath -Djava.util.logging.ConsoleHandler.level=INFO
187+
```
188+
189+
Run specific test with debug logging:
190+
191+
```bash
192+
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jsonpath -Dtest=JsonPathGoessnerTest -Djava.util.logging.ConsoleHandler.level=FINE
193+
```
194+
195+
## Limitations
196+
197+
1. **Script expressions**: Limited support - only `@.length-1` pattern
198+
2. **Logical operators in filters**: `&&`, `||`, `!` - implemented but not extensively tested
199+
3. **Deep nesting**: May cause stack overflow on extremely deep documents
200+
4. **Complex scripts**: No general JavaScript/expression evaluation
201+
202+
## Performance Considerations
203+
204+
1. **Recursive evaluation**: Uses Java call stack for segment traversal
205+
2. **Result collection**: Results collected in ArrayList during traversal
206+
3. **No caching**: Each query parses the path fresh
207+
4. **Defensive copies**: AST records create defensive copies for immutability

json-java21-jsonpath/pom.xml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
5+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<parent>
9+
<groupId>io.github.simbo1905.json</groupId>
10+
<artifactId>parent</artifactId>
11+
<version>0.1.9</version>
12+
</parent>
13+
14+
<artifactId>java.util.json.jsonpath</artifactId>
15+
<packaging>jar</packaging>
16+
<name>java.util.json Java21 Backport JsonPath</name>
17+
<url>https://simbo1905.github.io/java.util.json.Java21/</url>
18+
<scm>
19+
<connection>scm:git:https://github.com/simbo1905/java.util.json.Java21.git</connection>
20+
<developerConnection>scm:git:git@github.com:simbo1905/java.util.json.Java21.git</developerConnection>
21+
<url>https://github.com/simbo1905/java.util.json.Java21</url>
22+
<tag>HEAD</tag>
23+
</scm>
24+
<description>JsonPath implementation for the java.util.json Java 21 backport; parses JsonPath expressions to AST and matches against JSON documents.</description>
25+
26+
<properties>
27+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
28+
<maven.compiler.release>21</maven.compiler.release>
29+
</properties>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>io.github.simbo1905.json</groupId>
34+
<artifactId>java.util.json</artifactId>
35+
<version>${project.version}</version>
36+
</dependency>
37+
38+
<!-- Test dependencies -->
39+
<dependency>
40+
<groupId>org.junit.jupiter</groupId>
41+
<artifactId>junit-jupiter-api</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.junit.jupiter</groupId>
46+
<artifactId>junit-jupiter-engine</artifactId>
47+
<scope>test</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.junit.jupiter</groupId>
51+
<artifactId>junit-jupiter-params</artifactId>
52+
<scope>test</scope>
53+
</dependency>
54+
<dependency>
55+
<groupId>org.assertj</groupId>
56+
<artifactId>assertj-core</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
</dependencies>
60+
61+
<build>
62+
<plugins>
63+
<!-- Treat all warnings as errors, enable all lint warnings -->
64+
<plugin>
65+
<groupId>org.apache.maven.plugins</groupId>
66+
<artifactId>maven-compiler-plugin</artifactId>
67+
<version>3.11.0</version>
68+
<configuration>
69+
<release>21</release>
70+
<compilerArgs>
71+
<arg>-Xlint:all</arg>
72+
<arg>-Werror</arg>
73+
<arg>-Xdiags:verbose</arg>
74+
</compilerArgs>
75+
</configuration>
76+
</plugin>
77+
<plugin>
78+
<groupId>org.apache.maven.plugins</groupId>
79+
<artifactId>maven-surefire-plugin</artifactId>
80+
<configuration>
81+
<argLine>-ea</argLine>
82+
</configuration>
83+
</plugin>
84+
</plugins>
85+
</build>
86+
</project>

0 commit comments

Comments
 (0)