Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3faec69
Issue #136 Document experimental JTD ESM codegen CLI
cursoragent Feb 5, 2026
7b71cc5
Issue #136 Add jtd-esm-codegen module and CLI generator
cursoragent Feb 5, 2026
82d8ddd
Issue #136 Fix JTD AST packaging to avoid javac warnings
cursoragent Feb 5, 2026
5975aab
Issue #136 Add nightly release workflow for jtd-esm-codegen
cursoragent Feb 5, 2026
be9c170
Issue #136 Make generated ESM output deterministic
cursoragent Feb 5, 2026
9c9d5e2
exhaustive tests
simbo1905 Feb 7, 2026
b19a9f7
Replace bun with junit-js for JS tests; fix inline validator generati…
simbo1905 Feb 7, 2026
52f92e1
Update CI test count to reflect new junit-js tests (639 total, 5 skip…
simbo1905 Feb 7, 2026
d058788
Refactor JtdToEsmCodegenTest to use GraalVM Polyglot instead of bun
simbo1905 Feb 7, 2026
c1805a9
JTD bytecode codegen via ClassFile API + RFC 8927 conformance
simbo1905 Feb 8, 2026
4364b0f
son-java21-transforms
simbo1905 Feb 8, 2026
a80cc68
Rename json-java21-transforms to json-java21-jdt (JSON Document Trans…
simbo1905 Feb 8, 2026
4f8e79f
Update CI test count to 673 tests, 0 skipped (added 33 JDT tests, 1 j…
simbo1905 Feb 8, 2026
a6195f0
Add .DS_Store to .gitignore and create README.md and AGENTS.md for js…
simbo1905 Feb 8, 2026
5be3a28
Clarify JDT means JSON Document Transforms, not Eclipse JDT
simbo1905 Feb 8, 2026
f629733
Merge origin/jtd-gen into jdt-transforms (resolve pom.xml module list…
simbo1905 Feb 8, 2026
3ca2b83
Add json-java21-jsonpath-codegen module: bytecode-generated JsonPath …
simbo1905 Feb 8, 2026
91cabac
Fix CI: exclude Java 24+ codegen modules from Java 21 build, fix logg…
simbo1905 Feb 8, 2026
8f8f5a3
Update CI test count to 1018 (JTD property tests expand in surefire XML)
simbo1905 Feb 8, 2026
c0452aa
Make JDT path resolution pluggable for compiled JsonPath support
simbo1905 Feb 8, 2026
40dd65c
Add JDT AST sealed interface hierarchy and parser
simbo1905 Feb 8, 2026
f39d837
Add JsonPath ESM renderer: AST to ES2020 module codegen
simbo1905 Feb 8, 2026
b96335a
Add JDT ESM renderer: AST to ES2020 module codegen
simbo1905 Feb 8, 2026
8f0032e
Externalize JDT test data
simbo1905 Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
cache: 'maven'

- name: Build and verify
run: mvn -B -DskipITs=false -DskipTests=false verify
run: mvn -B -DskipITs=false -DskipTests=false verify -pl '!json-java21-jtd-codegen,!json-java21-jsonpath-codegen'

- name: Assert test count (no tests silently skipped)
run: |
Expand All @@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
exp_tests=611
exp_tests=1018
exp_skipped=0
if totals['tests']!=exp_tests or totals['skipped']!=exp_skipped:
print(f"Unexpected test totals: {totals} != expected tests={exp_tests}, skipped={exp_skipped}")
Expand Down
142 changes: 142 additions & 0 deletions .github/workflows/jtd-esm-codegen-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: JTD-ESM-Codegen Nightly

on:
schedule:
- cron: '0 3 * * *' # 3 AM UTC daily
workflow_dispatch:

permissions:
contents: write

jobs:
build-uber-jar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Build uber JAR
run: |
./mvnw -pl jtd-esm-codegen -am package
cp jtd-esm-codegen/target/jtd-esm-codegen.jar jtd-esm-codegen.jar
- uses: actions/upload-artifact@v4
with:
name: uber-jar
path: jtd-esm-codegen.jar

native-linux:
needs: build-uber-jar
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: uber-jar
path: .
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
components: 'native-image'
cache: 'maven'
- name: Build native image
run: |
native-image --no-fallback -jar jtd-esm-codegen.jar jtd-esm-codegen-linux-amd64
chmod +x jtd-esm-codegen-linux-amd64
- uses: actions/upload-artifact@v4
with:
name: native-linux-amd64
path: jtd-esm-codegen-linux-amd64

native-windows:
needs: build-uber-jar
runs-on: windows-latest
steps:
- uses: actions/download-artifact@v4
with:
name: uber-jar
path: .
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
components: 'native-image'
cache: 'maven'
- name: Build native image
run: |
native-image --no-fallback -jar jtd-esm-codegen.jar jtd-esm-codegen-windows-amd64
- uses: actions/upload-artifact@v4
with:
name: native-windows-amd64
path: jtd-esm-codegen-windows-amd64.exe

native-macos-intel:
needs: build-uber-jar
runs-on: macos-13 # Intel
steps:
- uses: actions/download-artifact@v4
with:
name: uber-jar
path: .
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
components: 'native-image'
cache: 'maven'
- name: Build native image
run: |
native-image --no-fallback -jar jtd-esm-codegen.jar jtd-esm-codegen-macos-amd64
chmod +x jtd-esm-codegen-macos-amd64
- uses: actions/upload-artifact@v4
with:
name: native-macos-amd64
path: jtd-esm-codegen-macos-amd64

native-macos-arm:
needs: build-uber-jar
runs-on: macos-14 # Apple Silicon
steps:
- uses: actions/download-artifact@v4
with:
name: uber-jar
path: .
- uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
components: 'native-image'
cache: 'maven'
- name: Build native image
run: |
native-image --no-fallback -jar jtd-esm-codegen.jar jtd-esm-codegen-macos-arm64
chmod +x jtd-esm-codegen-macos-arm64
- uses: actions/upload-artifact@v4
with:
name: native-macos-arm64
path: jtd-esm-codegen-macos-arm64

release:
needs: [native-linux, native-windows, native-macos-intel, native-macos-arm]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- name: Create nightly release
uses: softprops/action-gh-release@v1
with:
tag_name: nightly-${{ github.run_number }}
name: Nightly Build ${{ github.run_number }}
prerelease: true
files: |
native-linux-amd64/jtd-esm-codegen-linux-amd64
native-windows-amd64/jtd-esm-codegen-windows-amd64.exe
native-macos-amd64/jtd-esm-codegen-macos-amd64
native-macos-arm64/jtd-esm-codegen-macos-arm64
uber-jar/jtd-esm-codegen.jar

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ pom.xml.versionsBackup
# Local JTD debug logs
jtd*.log
**/jtd*.log

# macOS metadata
.DS_Store
**/.DS_Store
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ In addition to the core backport, this repo includes implementations of more adv
| --- | --- | --- |
| `json-java21-jtd` | JSON Type Definition (JTD) validator implementing RFC 8927 | [JTD validator](#json-type-definition-jtd-validator) |
| `json-java21-jsonpath` | JsonPath query engine over `java.util.json` values | [JsonPath](#jsonpath) |
| `jtd-esm-codegen` | Experimental JTD → ES2020 ESM validator code generator | [JTD → ESM codegen](#jtd-to-esm-validator-codegen-experimental) |

We welcome contributions to these incubating modules.

Expand Down Expand Up @@ -388,6 +389,38 @@ Features:
- ✅ Discriminator tag exemption from additional properties
- ✅ Stack-based validation preventing StackOverflowError

## JTD to ESM Validator Codegen (Experimental)

This repo also contains an **experimental** CLI tool that reads a JTD schema (RFC 8927) and generates a **vanilla ES2020 module** exporting a `validate(instance)` function. The intended use case is validating JSON event payloads in the browser (for example, across tabs using `BroadcastChannel`) without a build step.

### Supported JTD subset (flat schemas only)

This tool deliberately supports only:
- `properties` (required properties)
- `optionalProperties`
- `type` primitives (`string`, `boolean`, `timestamp`, `int8`, `int16`, `int32`, `uint8`, `uint16`, `uint32`, `float32`, `float64`)
- `enum`
- `metadata.id` (used for the output filename prefix)

It rejects other JTD features (`elements`, `values`, `discriminator`/`mapping`, `ref`/`definitions`) and also rejects **nested `properties`** (object schemas inside properties).

When rejected, the error message is:

`Unsupported JTD feature: <feature>. This experimental tool only supports flat schemas with properties, optionalProperties, type, and enum.`

### Build and run

```bash
./mvnw -pl jtd-esm-codegen -am package
java -jar ./jtd-esm-codegen/target/jtd-esm-codegen.jar schema.jtd.json
```

The output file is written to the current directory as:

`<metadata.id>-<sha256_prefix_8>.js`

Where `<sha256_prefix_8>` is the first 8 characters of the SHA-256 hash of the input schema file bytes.

## Building

Requires JDK 21 or later. Build with Maven:
Expand Down
87 changes: 87 additions & 0 deletions json-java21-jdt/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# json-java21-jdt/AGENTS.md

This file is for contributor/agent operational notes. Read `json-java21-jdt/README.md` for purpose, supported syntax, and user-facing examples.

## CRITICAL: What "JDT" Means in This Project

**JDT stands for "JSON Document Transforms"** -- a C# technology from Microsoft that we are porting to Java. It has NOTHING to do with Eclipse JDT (Java Development Tools). There are NO Eclipse files in this module. Do not confuse `json-java21-jdt` with Eclipse tooling. Every file in this module relates exclusively to JSON Document Transforms.

- User docs MUST recommend only `./mvnw`.
- 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.

## Stable Code Entry Points

- `json-java21-jdt/src/main/java/json/java21/jdt/Jdt.java` -- Public API: `Jdt.transform(source, transform)`
- `json-java21-jdt/src/main/java/json/java21/jdt/JdtException.java` -- Exception for invalid transforms

## Dependencies

- **`json-java21`** (core): The `JsonValue` sealed type hierarchy (`jdk.sandbox.java.util.json.*`)
- **`json-java21-jsonpath`**: `JsonPath.parse(path).query(source)` for `@jdt.path`-based operations

Both are compile-time dependencies. Changes to either module's public API may require updates here.

## Architecture Notes

### Current Implementation (Static Walker)

The engine currently has **no AST**. It works directly on `JsonValue` trees, interpreting `@jdt.*` keys on-the-fly via a recursive static-method walker:

1. `Jdt.transform(source, transform)` -- public entry point
2. `applyTransform(source, transform)` -- dispatches: non-object replaces, object checks for `@jdt.*` directives
3. `applyJdtDirectives(source, transformObj)` -- applies verbs in order: Rename -> Remove -> Merge -> Replace
4. Non-JDT keys in the transform object are processed as recursive default-merge operations

### Path-Based Operations

Path operations follow the pattern:
1. Parse JSONPath string: `JsonPath.parse(pathString)`
2. Query source: `jsonPath.query(source)` returns `List<JsonValue>`
3. Transform matched nodes using reference identity (`node == match`) to find and replace

**Known limitation**: Reference identity matching is fragile. It works only because JsonPath returns the exact same object instances from the source tree (not copies).

### Incomplete Features

- **Path-based rename**: `applyRenameWithPath` is a stub (returns source unchanged). The TODO is at `Jdt.java:234`.
- **~20 skipped Microsoft fixtures**: All path-based operation fixtures are in `Skipped/` subdirectories.

## Transform Verb Processing

Directive | Method | Notes
---|---|---
`@jdt.rename` | `applyRename` -> `applyRenameMapping` / `applyRenameWithPath` | Path-based is a stub
`@jdt.remove` | `applyRemove` -> `removeKey` / `applyRemoveWithPath` | Path-based uses `removeMatchedNodes`
`@jdt.merge` | `applyMerge` -> `mergeObjects` / `applyMergeWithPath` | Supports double-bracket arrays
`@jdt.replace` | `applyReplace` -> `applyReplaceWithPath` | Supports double-bracket arrays

## Test Fixtures

Microsoft JDT fixtures are vendored into:
```
json-java21-jdt/src/test/resources/microsoft-json-document-transforms/Inputs/
```

Convention: `{Category}.Source.json` + `{TestName}.Transform.json` + `{TestName}.Expected.json`

Fixtures in `Skipped/` subdirectories are excluded by `MicrosoftJdtFixturesTest` (line 42 filter). Move fixtures out of `Skipped/` as path-based operations are implemented.

## When Changing Transform Behavior

- Update `Jdt.java` (the engine).
- Add unit tests in `JdtTest.java` for the new behavior.
- Move applicable fixtures from `Skipped/` to the active directory if path-based operations are now supported.
- New tests MUST extend `JdtLoggingConfig`.
- Verify the full build passes: test counts in `.github/workflows/ci.yml` must match.

## Future Direction: AST and Code Generation

The planned evolution follows the same pattern as `jtd-esm-codegen`:

1. **Parse** the transform JSON into a sealed JDT AST (algebraic data types)
2. **Walk the AST** with exhaustive switch expressions to generate code
3. **Bytecode codegen**: Compile JDT transforms into generated Java classes
4. **ESM codegen**: Generate ES2020 JavaScript modules from the JDT AST
5. **Compiled JSONPath**: Replace the static JsonPath walker with compiled functions

This aligns with the project-wide "AST -> codegen" metaprogramming pattern.
Loading
Loading