Skip to content

Commit a6195f0

Browse files
committed
Add .DS_Store to .gitignore and create README.md and AGENTS.md for json-java21-jdt
To verify: mvn test -pl json-java21-jdt -am -Djava.util.logging.ConsoleHandler.level=INFO
1 parent 4f8e79f commit a6195f0

File tree

3 files changed

+336
-0
lines changed

3 files changed

+336
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ pom.xml.versionsBackup
4040
# Local JTD debug logs
4141
jtd*.log
4242
**/jtd*.log
43+
44+
# macOS metadata
45+
.DS_Store
46+
**/.DS_Store

json-java21-jdt/AGENTS.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# json-java21-jdt/AGENTS.md
2+
3+
This file is for contributor/agent operational notes. Read `json-java21-jdt/README.md` for purpose, supported syntax, and user-facing examples.
4+
5+
- User docs MUST recommend only `./mvnw`.
6+
- The `$(command -v mvnd || command -v mvn || command -v ./mvnw)` wrapper is for local developer speed only; do not put it in user-facing docs.
7+
8+
## Stable Code Entry Points
9+
10+
- `json-java21-jdt/src/main/java/json/java21/jdt/Jdt.java` -- Public API: `Jdt.transform(source, transform)`
11+
- `json-java21-jdt/src/main/java/json/java21/jdt/JdtException.java` -- Exception for invalid transforms
12+
13+
## Dependencies
14+
15+
- **`json-java21`** (core): The `JsonValue` sealed type hierarchy (`jdk.sandbox.java.util.json.*`)
16+
- **`json-java21-jsonpath`**: `JsonPath.parse(path).query(source)` for `@jdt.path`-based operations
17+
18+
Both are compile-time dependencies. Changes to either module's public API may require updates here.
19+
20+
## Architecture Notes
21+
22+
### Current Implementation (Static Walker)
23+
24+
The engine currently has **no AST**. It works directly on `JsonValue` trees, interpreting `@jdt.*` keys on-the-fly via a recursive static-method walker:
25+
26+
1. `Jdt.transform(source, transform)` -- public entry point
27+
2. `applyTransform(source, transform)` -- dispatches: non-object replaces, object checks for `@jdt.*` directives
28+
3. `applyJdtDirectives(source, transformObj)` -- applies verbs in order: Rename -> Remove -> Merge -> Replace
29+
4. Non-JDT keys in the transform object are processed as recursive default-merge operations
30+
31+
### Path-Based Operations
32+
33+
Path operations follow the pattern:
34+
1. Parse JSONPath string: `JsonPath.parse(pathString)`
35+
2. Query source: `jsonPath.query(source)` returns `List<JsonValue>`
36+
3. Transform matched nodes using reference identity (`node == match`) to find and replace
37+
38+
**Known limitation**: Reference identity matching is fragile. It works only because JsonPath returns the exact same object instances from the source tree (not copies).
39+
40+
### Incomplete Features
41+
42+
- **Path-based rename**: `applyRenameWithPath` is a stub (returns source unchanged). The TODO is at `Jdt.java:234`.
43+
- **~20 skipped Microsoft fixtures**: All path-based operation fixtures are in `Skipped/` subdirectories.
44+
45+
## Transform Verb Processing
46+
47+
Directive | Method | Notes
48+
---|---|---
49+
`@jdt.rename` | `applyRename` -> `applyRenameMapping` / `applyRenameWithPath` | Path-based is a stub
50+
`@jdt.remove` | `applyRemove` -> `removeKey` / `applyRemoveWithPath` | Path-based uses `removeMatchedNodes`
51+
`@jdt.merge` | `applyMerge` -> `mergeObjects` / `applyMergeWithPath` | Supports double-bracket arrays
52+
`@jdt.replace` | `applyReplace` -> `applyReplaceWithPath` | Supports double-bracket arrays
53+
54+
## Test Fixtures
55+
56+
Microsoft JDT fixtures are vendored into:
57+
```
58+
json-java21-jdt/src/test/resources/microsoft-json-document-transforms/Inputs/
59+
```
60+
61+
Convention: `{Category}.Source.json` + `{TestName}.Transform.json` + `{TestName}.Expected.json`
62+
63+
Fixtures in `Skipped/` subdirectories are excluded by `MicrosoftJdtFixturesTest` (line 42 filter). Move fixtures out of `Skipped/` as path-based operations are implemented.
64+
65+
## When Changing Transform Behavior
66+
67+
- Update `Jdt.java` (the engine).
68+
- Add unit tests in `JdtTest.java` for the new behavior.
69+
- Move applicable fixtures from `Skipped/` to the active directory if path-based operations are now supported.
70+
- New tests MUST extend `JdtLoggingConfig`.
71+
- Verify the full build passes: test counts in `.github/workflows/ci.yml` must match.
72+
73+
## Future Direction: AST and Code Generation
74+
75+
The planned evolution follows the same pattern as `jtd-esm-codegen`:
76+
77+
1. **Parse** the transform JSON into a sealed JDT AST (algebraic data types)
78+
2. **Walk the AST** with exhaustive switch expressions to generate code
79+
3. **Bytecode codegen**: Compile JDT transforms into generated Java classes
80+
4. **ESM codegen**: Generate ES2020 JavaScript modules from the JDT AST
81+
5. **Compiled JSONPath**: Replace the static JsonPath walker with compiled functions
82+
83+
This aligns with the project-wide "AST -> codegen" metaprogramming pattern.

json-java21-jdt/README.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# JSON Document Transforms (JDT)
2+
3+
A Java implementation of JSON Document Transforms, inspired by [Microsoft's JSON Document Transforms](https://github.com/Microsoft/json-document-transforms) specification. JDT transforms a source JSON document using a declarative transform specification document with `@jdt.*` directives.
4+
5+
## Features
6+
7+
- **Declarative Transforms**: Transform documents mirror source structure with `@jdt.*` directives
8+
- **Four Transform Verbs**: Rename, Remove, Merge, Replace
9+
- **Fixed Execution Order**: Rename -> Remove -> Merge -> Replace (predictable, composable)
10+
- **Recursive Default Merge**: Objects merge deeply, arrays append, primitives replace
11+
- **Path-Based Operations**: Target specific nodes using JSONPath via `@jdt.path`
12+
- **Immutable Design**: All operations return new values; source documents are never mutated
13+
- **Microsoft Fixture Compatibility**: Validates against vendored Microsoft JDT test fixtures
14+
15+
## Quick Start
16+
17+
```java
18+
import jdk.sandbox.java.util.json.*;
19+
import json.java21.jdt.Jdt;
20+
21+
JsonValue source = Json.parse("""
22+
{
23+
"ConnectionStrings": {
24+
"Default": "Server=dev;Database=mydb"
25+
},
26+
"Logging": {
27+
"Level": "Debug"
28+
}
29+
}
30+
""");
31+
32+
JsonValue transform = Json.parse("""
33+
{
34+
"ConnectionStrings": {
35+
"Default": "Server=prod;Database=mydb"
36+
},
37+
"Logging": {
38+
"Level": "Warning"
39+
}
40+
}
41+
""");
42+
43+
JsonValue result = Jdt.transform(source, transform);
44+
// Result: ConnectionStrings.Default updated, Logging.Level updated
45+
```
46+
47+
## Default Behavior (No Directives)
48+
49+
When the transform document does not contain `@jdt.*` directives, the default behavior applies:
50+
51+
- **Object to Object**: Deep recursive merge. Existing keys are updated, new keys are added, missing keys are preserved.
52+
- **Array to Array**: Arrays are appended (concatenated), not replaced.
53+
- **Primitive to Primitive**: The transform value replaces the source value.
54+
- **Non-object transform**: Replaces the source wholesale.
55+
56+
```java
57+
// Deep merge
58+
JsonValue source = Json.parse("""
59+
{"Settings": {"A": 1, "B": 2}}
60+
""");
61+
JsonValue transform = Json.parse("""
62+
{"Settings": {"A": 10, "C": 3}}
63+
""");
64+
JsonValue result = Jdt.transform(source, transform);
65+
// {"Settings": {"A": 10, "B": 2, "C": 3}}
66+
```
67+
68+
## Transform Verbs
69+
70+
### `@jdt.rename`
71+
72+
Renames keys without altering their values.
73+
74+
```json
75+
// Transform: rename "oldName" to "newName"
76+
{
77+
"@jdt.rename": {"oldName": "newName"}
78+
}
79+
```
80+
81+
The value can be:
82+
- **Object**: `{"oldKey": "newKey", ...}` for direct key mapping
83+
- **Array**: `[{"a": "b"}, {"c": "d"}]` for sequential renames
84+
85+
### `@jdt.remove`
86+
87+
Removes keys from the current node.
88+
89+
```json
90+
// Remove a single key
91+
{"@jdt.remove": "keyToRemove"}
92+
93+
// Remove multiple keys
94+
{"@jdt.remove": ["key1", "key2"]}
95+
96+
// Remove all keys (set to null)
97+
{"@jdt.remove": true}
98+
```
99+
100+
The value can be:
101+
- **String**: Single key name to remove
102+
- **Array**: List of key names to remove
103+
- **Boolean `true`**: Remove all keys (returns `null`)
104+
- **Object with `@jdt.path`**: Path-based removal using JSONPath
105+
106+
### `@jdt.merge`
107+
108+
Explicitly deep-merges an object (same semantics as default merge, but stated explicitly).
109+
110+
```json
111+
// Explicit merge
112+
{
113+
"Settings": {
114+
"@jdt.merge": {"newSetting": "value"}
115+
}
116+
}
117+
```
118+
119+
### `@jdt.replace`
120+
121+
Wholesale replaces the current node.
122+
123+
```json
124+
// Replace with a primitive
125+
{"@jdt.replace": 42}
126+
127+
// Replace with an object
128+
{"@jdt.replace": {"completely": "new"}}
129+
130+
// Replace with an array (double-bracket syntax)
131+
{"@jdt.replace": [[1, 2, 3]]}
132+
```
133+
134+
The double-bracket syntax `[[...]]` disambiguates "replace with this array" from "apply sequential operations".
135+
136+
## Execution Order
137+
138+
Within any single node, directives are applied in this fixed order:
139+
140+
**Rename -> Remove -> Merge -> Replace**
141+
142+
This means:
143+
1. Renames happen first, so subsequent operations reference the new names
144+
2. Remove runs on the already-renamed object
145+
3. Merge adds/updates keys on the pruned object
146+
4. Replace is last and can override everything
147+
148+
```java
149+
// Combined: rename then remove
150+
JsonValue transform = Json.parse("""
151+
{
152+
"@jdt.rename": {"A": "Alpha"},
153+
"@jdt.remove": "B"
154+
}
155+
""");
156+
```
157+
158+
## Path-Based Operations
159+
160+
Use `@jdt.path` with JSONPath syntax to target specific nodes within the document.
161+
162+
```json
163+
{
164+
"@jdt.remove": {
165+
"@jdt.path": "$.items[?(@.active == false)]"
166+
}
167+
}
168+
```
169+
170+
```json
171+
{
172+
"@jdt.replace": {
173+
"@jdt.path": "$.settings.timeout",
174+
"@jdt.value": 30
175+
}
176+
}
177+
```
178+
179+
Path-based operations are supported for remove, merge, and replace. Path-based rename is a work in progress.
180+
181+
## Error Handling
182+
183+
Invalid transform specifications throw `JdtException`:
184+
185+
```java
186+
try {
187+
Jdt.transform(source, transform);
188+
} catch (JdtException e) {
189+
System.err.println("Transform error: " + e.getMessage());
190+
}
191+
```
192+
193+
Null inputs throw `NullPointerException` with descriptive messages.
194+
195+
## Building and Testing
196+
197+
```bash
198+
# Build the module
199+
./mvnw compile -pl json-java21-jdt -am
200+
201+
# Run all tests
202+
./mvnw test -pl json-java21-jdt -am -Djava.util.logging.ConsoleHandler.level=INFO
203+
204+
# Run unit tests only
205+
./mvnw test -pl json-java21-jdt -am -Dtest=JdtTest -Djava.util.logging.ConsoleHandler.level=FINE
206+
207+
# Run Microsoft fixture tests
208+
./mvnw test -pl json-java21-jdt -am -Dtest=MicrosoftJdtFixturesTest -Djava.util.logging.ConsoleHandler.level=FINE
209+
```
210+
211+
## Architecture
212+
213+
The engine is a single static utility class (`Jdt`) that walks the transform document recursively:
214+
215+
- **Immutable Functional Core**: Every method takes `JsonValue` inputs and returns new `JsonValue` outputs
216+
- **Recursive Walker**: The transform document is interpreted on-the-fly as it is walked
217+
- **JSONPath Integration**: Path-based operations use the `json-java21-jsonpath` module for node selection
218+
- **Reference Identity Matching**: Path-matched nodes are found in the source tree by reference identity
219+
220+
### Source Files
221+
222+
File | Role
223+
---|---
224+
`Jdt.java` | Core transform engine (public API: `Jdt.transform`)
225+
`JdtException.java` | Exception for invalid transform specifications
226+
227+
### Test Files
228+
229+
File | Role
230+
---|---
231+
`JdtTest.java` | Unit tests for each verb and default behavior (18 tests)
232+
`MicrosoftJdtFixturesTest.java` | Parameterized tests using vendored Microsoft JDT fixtures (15 tests)
233+
`JdtLoggingConfig.java` | JUL logging configuration base class
234+
235+
## Microsoft JDT Fixture Status
236+
237+
Category | Active | Skipped (path-based)
238+
---|---|---
239+
Default | 10 | 0
240+
Remove | 3 | 8
241+
Rename | 3 | 7
242+
Replace | 4 | 3
243+
Merge | 5 | 3
244+
245+
Fixtures in `Skipped/` subdirectories require path-based operations that are planned for future implementation.
246+
247+
## License
248+
249+
This project is part of the OpenJDK JSON API implementation and follows the same licensing terms.

0 commit comments

Comments
 (0)