Skip to content

Commit 3cc73b0

Browse files
authored
Issue #145 sync backport to the jdk.incubator.json module (43325738c)
Squash of PR #152 (branch issue-145-track-jdk-incubator-json). - Point the API tracker at the new upstream location (src/jdk.incubator.json module, frontier 43325738c) - Mechanical package move jdk.sandbox -> jdk.incubator (git mv + sed, all modules) - Uplift upstream API+impl sources at 43325738c with mechanical transforms (JsonValueException, JsonGenerator, JsonValueSupport, iterative parser, LazyConstant) - Apply upstream API renames (asBoolean/asString/asInt/asLong/asDouble/asList/asMap/get/tryGet/tryValue, remove subtype equals/hashCode, toDisplayString(String indent)) - Port upstream test suite (+304), harden number-math boundaries (+11), wire ReadmeExamplesTest, remove dead code - Docs: frontier anchor bumped to 43325738c, Migration Notice updated, ci.yml exp_tests=1676 Verify: mvnd clean verify -> 1676 tests, 0 failures/errors/skipped; all PR CI checks green. Closes #145
1 parent 754fe3e commit 3cc73b0

127 files changed

Lines changed: 4817 additions & 2219 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/copilot-instructions.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ return switch (jsonValue) {
8181
```
8282

8383
### Project Architecture
84-
- Core package: `jdk.sandbox.java.util.json`
85-
- Internal implementation: `jdk.sandbox.internal.util.json`
84+
- Core package: `jdk.incubator.java.util.json`
85+
- Internal implementation: `jdk.incubator.internal.util.json`
8686
- JSON Schema validator in separate module
8787
- Use appropriate logging configuration per module
8888

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
for k in totals: totals[k]+=int(r.get(k,'0'))
4040
except Exception:
4141
pass
42-
exp_tests=1355
42+
exp_tests=1676
4343
exp_skipped=0
4444
if totals['tests']!=exp_tests or totals['skipped']!=exp_skipped:
4545
print(f"Unexpected test totals: {totals} != expected tests={exp_tests}, skipped={exp_skipped}")

‎AGENTS.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ throw new IllegalArgumentException("enum contains duplicate values: " +
124124

125125
// Include the problematic schema portion
126126
throw new IllegalArgumentException("Type schema contains unknown key: " + key +
127-
" in schema: " + Json.toDisplayString(obj, 0));
127+
" in schema: " + Json.toDisplayString(obj, ""));
128128

129129
// Include both expected and actual values
130130
throw new IllegalArgumentException("unknown type: '" + typeStr +
@@ -138,7 +138,7 @@ throw new IllegalArgumentException("invalid schema"); // Too vague
138138
throw new IllegalArgumentException("bad value"); // No specifics
139139
```
140140

141-
Use `Json.toDisplayString(value, depth)` to render JSON fragments in error messages, and include relevant context like schema paths, actual vs expected values, and specific constraint violations.
141+
Use `Json.toDisplayString(value, indent)` to render JSON fragments in error messages, and include relevant context like schema paths, actual vs expected values, and specific constraint violations.
142142

143143
## JSON Compatibility Suite
144144

@@ -151,15 +151,15 @@ See `README.md` for user-facing commands. When running locally as an agent, use
151151
- `json-java21-api-tracker`: API evolution tracking utilities.
152152
- `json-compatibility-suite`: JSON Test Suite compatibility validation.
153153
- `json-java21-jtd`: JSON Type Definition (JTD) validator based on RFC 8927.
154-
- `json-java21-jsonpath`: JsonPath query engine over `jdk.sandbox.java.util.json` values.
154+
- `json-java21-jsonpath`: JsonPath query engine over `jdk.incubator.java.util.json` values.
155155

156156
Only when you are asked to work on a specific module, start by reading that module's `README.md`, then its `AGENTS.md`.
157157

158158
These modules are treated as separate subsystems; do not read their docs unless you are actively working on them. They are not interlinked and each depends only on the core `json-java21` API.
159159

160160
### Core Components
161161

162-
#### Public API (`jdk.sandbox.java.util.json`)
162+
#### Public API (`jdk.incubator.java.util.json`)
163163
- `Json`: Static utilities for parsing, formatting, and conversion.
164164
- `JsonValue`: Sealed root interface for all JSON types.
165165
- `JsonObject`: JSON objects (key-value pairs).
@@ -169,14 +169,14 @@ These modules are treated as separate subsystems; do not read their docs unless
169169
- `JsonBoolean`: JSON booleans.
170170
- `JsonNull`: JSON null.
171171

172-
IMPORTANT: This API **MUST NOT** deviate from the upstream jdk.sandbox repo which is will track.
172+
IMPORTANT: This API **MUST NOT** deviate from the upstream jdk.incubator repo which is will track.
173173

174-
#### Internal Implementation (`jdk.sandbox.internal.util.json`)
174+
#### Internal Implementation (`jdk.incubator.internal.util.json`)
175175
- `JsonParser`: Recursive descent JSON parser.
176176
- `Json*Impl`: Immutable implementations of `Json*` types.
177177
- `Utils`: Internal utilities and factory methods.
178178

179-
IMPORTANT: Bugs in upstream-derived core logic MUST be fixed upstream. Do not patch `jdk.sandbox.*` sources in this repo unless the user explicitly agrees (for example, to carry a temporary local backport while the upstream fix is in progress).
179+
IMPORTANT: Bugs in upstream-derived core logic MUST be fixed upstream. Do not patch `jdk.incubator.*` sources in this repo unless the user explicitly agrees (for example, to carry a temporary local backport while the upstream fix is in progress).
180180

181181
Only bugs in local, non-upstream code (for example, backporting shims/polyfills or other modules in this repo) should be fixed here using normal TDD.
182182

@@ -480,7 +480,7 @@ flowchart LR
480480
python3 - <<'PY'
481481
import os, sys, re
482482
src = 'updates/2025-09-04/upstream/jdk.internal.util.json'
483-
dst = 'json-java21/src/main/java/jdk/sandbox/internal/util/json'
483+
dst = 'json-java21/src/main/java/jdk/incubator/internal/util/json'
484484
def xform(text):
485485
# old old python3 stuff here
486486
print('OK')

‎README.md‎

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This repo is organized into the following modules:
1717
| `json-java21-jtd` | JTD (RFC 8927) stack-machine interpreter — ideal for infrequent config parsing and one-time validation | 21+ |
1818
| `json-java21-jtd-codegen` | Bytecode code generator for JTD schemas — ahead-of-time compiled validators for repeated hot-path validation | 24+ (auto-skipped on JDK 21) |
1919
| `jdt2jar` | CLI + distroless container to pre-compile JTD schemas into standalone validator JARs (eliminates JDK 24+ runtime requirement) | 24+ (auto-skipped on JDK 21) |
20-
| `json-java21-jsonpath` | JsonPath query engine over `jdk.sandbox.java.util.json` values (Goessner-style: filters, slices, recursive descent, unions) | 21+ |
20+
| `json-java21-jsonpath` | JsonPath query engine over `jdk.incubator.java.util.json` values (Goessner-style: filters, slices, recursive descent, unions) | 21+ |
2121
| `json-compatibility-suite` | JSON Test Suite conformance reporter (tests against [nst/JSONTestSuite](https://github.com/nst/JSONTestSuite)) | 21+ |
2222
| `json-java21-api-tracker` | Daily upstream API drift detector — fetches OpenJDK sandbox sources, compares public API signatures, reports differences | 25+ |
2323

@@ -31,7 +31,7 @@ To try the examples from this README, build the project and run the standalone e
3131

3232
```bash
3333
./mvnw package
34-
java -cp ./json-java21/target/test-classes/:./json-java21/target/classes/ jdk.sandbox.java.util.json.examples.ReadmeExamples
34+
java -cp ./json-java21/target/test-classes/:./json-java21/target/classes/ jdk.incubator.java.util.json.examples.ReadmeExamples
3535
```
3636

3737
## API Overview
@@ -60,9 +60,9 @@ JsonValue value = Json.parse(json);
6060

6161
// Access as map-like structure
6262
JsonObject obj = (JsonObject) value;
63-
String name = ((JsonString) obj.members().get("name")).string();
64-
long age = ((JsonNumber) obj.members().get("age")).toLong();
65-
boolean active = ((JsonBoolean) obj.members().get("active")).bool();
63+
String name = ((JsonString) obj.asMap().get("name")).asString();
64+
long age = ((JsonNumber) obj.asMap().get("age")).asLong();
65+
boolean active = ((JsonBoolean) obj.asMap().get("active")).asBoolean();
6666
```
6767

6868
### Simple Record Mapping
@@ -77,9 +77,9 @@ JsonObject jsonObj = (JsonObject) Json.parse(userJson);
7777

7878
// Map to record
7979
User user = new User(
80-
((JsonString) jsonObj.members().get("name")).string(),
81-
((JsonNumber) jsonObj.members().get("age")).toLong(),
82-
((JsonBoolean) jsonObj.members().get("active")).bool()
80+
((JsonString) jsonObj.asMap().get("name")).asString(),
81+
((JsonNumber) jsonObj.asMap().get("age")).asLong(),
82+
((JsonBoolean) jsonObj.asMap().get("active")).asBoolean()
8383
);
8484

8585
// Convert records back to JSON using typed factories
@@ -117,20 +117,22 @@ JsonValue parsed = Json.parse("{\"name\":\"John\",\"age\":30}");
117117
JsonObject obj = (JsonObject) parsed;
118118

119119
// Use the new type-safe accessor methods
120-
String name = obj.get("name").string(); // Returns "John"
121-
long age = obj.get("age").toLong(); // Returns 30L
122-
double ageDouble = obj.get("age").toDouble(); // Returns 30.0
120+
String name = obj.get("name").asString(); // Returns "John"
121+
long age = obj.get("age").asLong(); // Returns 30L
122+
double ageDouble = obj.get("age").asDouble(); // Returns 30.0
123123
```
124124

125125
The accessor methods on `JsonValue`:
126-
- `string()` - Returns the String value (for JsonString)
127-
- `toLong()` - Returns the long value (for JsonNumber, if representable)
128-
- `toDouble()` - Returns the double value (for JsonNumber, if representable)
129-
- `bool()` - Returns the boolean value (for JsonBoolean)
130-
- `elements()` - Returns List<JsonValue> (for JsonArray)
131-
- `members()` - Returns Map<String, JsonValue> (for JsonObject)
126+
- `asString()` - Returns the String value (for JsonString)
127+
- `asLong()` - Returns the long value (for JsonNumber, if representable)
128+
- `asDouble()` - Returns the double value (for JsonNumber, if representable)
129+
- `asBoolean()` - Returns the boolean value (for JsonBoolean)
130+
- `asList()` - Returns List<JsonValue> (for JsonArray)
131+
- `asMap()` - Returns Map<String, JsonValue> (for JsonObject)
132132
- `get(String name)` - Access JsonObject member by name
133-
- `element(int index)` - Access JsonArray element by index
133+
- `get(int index)` - Access JsonArray element by index
134+
- `tryGet(String name)` - Returns Optional<JsonValue> for a JsonObject member
135+
- `tryValue()` - Returns Optional<JsonValue>, empty for JsonNull
134136

135137
### Realistic Record Mapping
136138

@@ -162,14 +164,14 @@ JsonValue teamJson = JsonObject.of(Map.of(
162164
// Parse JSON back to records
163165
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
164166
Team reconstructed = new Team(
165-
((JsonString) parsed.members().get("teamName")).string(),
166-
((JsonArray) parsed.members().get("members")).elements().stream()
167+
((JsonString) parsed.asMap().get("teamName")).asString(),
168+
((JsonArray) parsed.asMap().get("members")).asList().stream()
167169
.map(v -> {
168170
JsonObject member = (JsonObject) v;
169171
return new User(
170-
((JsonString) member.members().get("name")).string(),
171-
((JsonString) member.members().get("email")).string(),
172-
((JsonBoolean) member.members().get("active")).bool()
172+
((JsonString) member.asMap().get("name")).asString(),
173+
((JsonString) member.asMap().get("email")).asString(),
174+
((JsonBoolean) member.asMap().get("active")).asBoolean()
173175
);
174176
})
175177
.toList()
@@ -206,10 +208,10 @@ Process JSON arrays efficiently with Java streams:
206208
```java
207209
// Filter active users from a JSON array
208210
JsonArray users = (JsonArray) Json.parse(jsonArrayString);
209-
List<String> activeUserEmails = users.elements().stream()
211+
List<String> activeUserEmails = users.asList().stream()
210212
.map(v -> (JsonObject) v)
211-
.filter(obj -> ((JsonBoolean) obj.members().get("active")).bool())
212-
.map(obj -> ((JsonString) obj.members().get("email")).string())
213+
.filter(obj -> ((JsonBoolean) obj.asMap().get("active")).asBoolean())
214+
.map(obj -> ((JsonString) obj.asMap().get("email")).asString())
213215
.toList();
214216
```
215217

@@ -222,9 +224,9 @@ try {
222224
JsonValue value = Json.parse(userInput);
223225
// Process valid JSON
224226
} catch (JsonParseException e) {
225-
// Handle malformed JSON with line/column information
226-
System.err.println("Invalid JSON at line " + e.getLine() +
227-
", column " + e.getColumn() + ": " + e.getMessage());
227+
// Handle malformed JSON with line/position information
228+
System.err.println("Invalid JSON at line " + e.getErrorLine() +
229+
", position " + e.getErrorPosition() + ": " + e.getMessage());
228230
}
229231
```
230232

@@ -242,7 +244,7 @@ JsonObject data = JsonObject.of(Map.of(
242244
))
243245
));
244246

245-
String formatted = Json.toDisplayString(data, 2);
247+
String formatted = Json.toDisplayString(data, " ");
246248
// Output:
247249
// {
248250
// "name": "Alice",
@@ -289,41 +291,47 @@ The test data is bundled as ZIP files and extracted automatically at runtime:
289291

290292
**Final `java.util.json` sandbox-era release** (2026-05-19).
291293

292-
This code is derived from the OpenJDK jdk-sandbox repository "json" branch at commit `c1a4f80` (2026-02-05), which was the last commit before the API was moved to `jdk.incubator.json`.
294+
This code is derived from the OpenJDK jdk-sandbox repository "json" branch at commit `43325738c` (2026-08-27), which is the current frontier of the incubator-era `jdk.incubator.json` API. The incubator promotion itself happened at commit `b956ae0` (2026-02-05); this branch completed the migration from the sandbox-era `java.util.json` naming to the incubator packages — see the notice below and issue #145.
293295

294296
### API Summary
295-
- `JsonValue` conversion methods: `asBoolean()`, `toInt()`, `toLong()`, `toDouble()`, `asString()`
296-
- `JsonValue` navigation methods: `get(String)`, `get(int)`, `getOrAbsent(String)`, `valueOrNull()`
297-
- `JsonArray`: `elements()`, `of(List)`
298-
- `JsonObject`: `members()`, `of(Map)`
299-
- `Json`: `parse(String)`, `parse(char[])`, `toDisplayString(JsonValue, int)`
297+
- `JsonValue` conversion methods: `asBoolean()`, `asString()`, `asInt()`, `asLong()`, `asDouble()`
298+
- `JsonValue` navigation methods: `get(String)`, `get(int)`, `tryGet(String)`, `tryValue()`
299+
- `JsonArray`: `asList()`, `of(List)`
300+
- `JsonObject`: `asMap()`, `of(Map)`
301+
- `Json`: `parse(String)`, `parse(char[])`, `toDisplayString(JsonValue, String indent)`
300302

301303
### Upstream Migration Notice
302-
The upstream `java.util.json` API has been promoted to `jdk.incubator.json` (commit `b956ae0`, 2026-02-05). The incubator version introduces significant API changes including method renames (`bool()`→`asBoolean()`, `string()`→`asString()`, etc.) and new methods (`asInt()`). A separate branch tracks the incubator upgrade — see issue #145.
304+
The upstream `java.util.json` API has been promoted to `jdk.incubator.json` (commit `b956ae0`, 2026-02-05). The incubator version introduces significant API changes including method renames (`bool()`→`asBoolean()`, `string()`→`asString()`, `toInt()`→`asInt()`, etc.), `tryGet()`/`tryValue()` navigation, and identity (non-value) `equals`/`hashCode`. **That migration is now DONE in this branch** (issue #145): the public API lives in `jdk.incubator.java.util.json` and the implementation in `jdk.incubator.internal.util.json`, matching upstream frontier `43325738c`.
303305

304306
The original proposal and design rationale can be found in the included PDF: [Towards a JSON API for the JDK.pdf](Towards%20a%20JSON%20API%20for%20the%20JDK.pdf)
305307

306308
The JSON compatibitlity tests in this repo suggest 99% conformance with a leading test suite when in "strict" mode. The two conformance expecatations that fail assume that duplicated keys in a JSON document are okay. The upstream code at this time appear to take a strict stance that it should not siliently ignore duplicate keys in a json object.
307309

308310
### CI: Upstream API Tracking
309311

310-
**Note**: The daily API tracker workflow currently targets the old `java.util.json` paths which no longer exist upstream. It needs to be updated to track `jdk.incubator.json` — see issue #145.
312+
The `daily-api-tracker.yml` workflow runs daily at 02:00 UTC: it fetches the upstream `jdk.incubator.json` sources from the [jdk-sandbox `json` branch](https://github.com/openjdk/jdk-sandbox/tree/json) HEAD and compares public API signatures against the local `jdk.incubator.java.util.json` classes. When they differ it creates a fingerprint-deduplicated "API drift detected" issue; reports are uploaded as workflow artifacts (`target/api-tracker/`) with 90-day retention. The check can also be run locally:
313+
314+
```bash
315+
$(command -v mvnd || command -v mvn || command -v ./mvnw) -pl json-java21-api-tracker exec:java \
316+
-Dexec.mainClass="io.github.simbo1905.tracker.ApiTrackerRunner" \
317+
-Dexec.args="INFO"
318+
```
311319

312320
## Modifications
313321

314322
This is a simplified backport with the following changes from the original:
315323
- Replaced `LazyConstant` with a package-local polyfill using double-checked locking pattern.
316324
- Added `Utils.powExact()` polyfill for `Math.powExact(long, int)` which is not available in Java 21.
317-
- Replaced unnamed variables `_` with `ignored` for Java 21 compatibility.
325+
- Replaced unnamed variables `_` with named variables (`e`, `v`, `k`) for Java 21 compatibility.
318326
- Removed `@ValueBased` annotations.
319327
- Removed `@PreviewFeature` annotations.
320328
- Compatible with JDK 21.
321329

322330
### Upstream Bug Fixes
323331

324-
The following fixes have been applied to address bugs in the upstream OpenJDK jdk-sandbox code. These are upstream issues that should be reported to the [core-libs-dev@openjdk.org](mailto:core-libs-dev@openjdk.org) mailing list per OpenJDK process:
332+
Historically this backport carried local fixes against the upstream OpenJDK jdk-sandbox code. With the uplift to upstream frontier `43325738c` their disposition is:
325333

326-
- **`JsonNumber.of(double)` offset bug** ([#118](https://github.com/simbo1905/java.util.json.Java21/issues/118)): The upstream implementation hardcodes `decimalOffset=0` and `exponentOffset=0`, causing `toLong()` to fail for integral doubles like `123.0`. Our fix delegates to `JsonNumber.of(String)` which correctly computes offsets via `Json.parse()`.
334+
- **`JsonNumber.of(double)` offset bug** ([#118](https://github.com/simbo1905/java.util.json.Java21/issues/118)): **CLOSED BY UPSTREAM — no longer carried.** Upstream reworked the numeric logic: `of(double)` now computes the decimal/exponent offsets from `Double.toString` output via `indexOf`, and `JsonNumberImpl` was rewritten with `LazyConstant`-cached conversions, trailing-zero stripping and sign/scale handling. Verified equivalent to our historic `of(String)` delegation fix by `JsonNumberOfDoubleMatrixTest` (integral doubles such as `123.0` and `1.0E2`, fractions, negatives, zero variants, out-of-range `asInt`/`asLong` throwing `JsonValueException`, and very large/small magnitudes) plus the ported upstream `TestJsonNumber`. The historic delegation hack has been removed with the uplifted upstream source.
327335

328336
## Security Considerations
329337

@@ -355,7 +363,7 @@ Per **RFC 8927 (JSON Typedef)**, the empty schema `{}` is the **empty form** and
355363
356364
```java
357365
import json.java21.jtd.Jtd;
358-
import jdk.sandbox.java.util.json.*;
366+
import jdk.incubator.java.util.json.*;
359367

360368
JsonValue schema = Json.parse("{\"properties\":{\"name\":{\"type\":\"string\"}}}");
361369
JsonValue data = Json.parse("{\"name\":\"Alice\"}");
@@ -394,7 +402,7 @@ This repo also includes a JsonPath query engine (module `json-java21-jsonpath`),
394402
https://goessner.net/articles/JsonPath/
395403

396404
```java
397-
import jdk.sandbox.java.util.json.*;
405+
import jdk.incubator.java.util.json.*;
398406
import json.java21.jsonpath.JsonPath;
399407
import json.java21.jsonpath.JsonPathStreams;
400408

@@ -409,7 +417,7 @@ JsonValue doc = Json.parse("""
409417
var authors = JsonPath.parse("$.store.book[*].author")
410418
.query(doc)
411419
.stream()
412-
.map(JsonValue::string)
420+
.map(JsonValue::asString)
413421
.toList();
414422

415423
System.out.println("Authors count: " + authors.size()); // prints '3'
@@ -419,7 +427,7 @@ System.out.println("Last author: " + authors.getLast()); // prints 'Marek Ily
419427
var cheapTitles = JsonPath.parse("$.store.book[?(@.price < 10)].title")
420428
.query(doc)
421429
.stream()
422-
.map(JsonValue::string)
430+
.map(JsonValue::asString)
423431
.toList();
424432

425433
var priceStats = JsonPath.parse("$.store.book[*].price")

0 commit comments

Comments
 (0)