Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
exp_tests=509
exp_tests=507
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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# Eclipse/IDE files
.project
.classpath
.settings/
json-compatibility-suite/.classpath
json-compatibility-suite/.project
json-compatibility-suite/.settings/
json-java21/.classpath
json-java21/.project
json-java21/.settings/
json-java21-api-tracker/.classpath
json-java21-api-tracker/.project
json-java21-api-tracker/.settings/
json-java21-jtd/.classpath
json-java21-jtd/.project
json-java21-jtd/.settings/
json-java21-schema/src/test/resources/draft4/
json-java21-schema/src/test/resources/json-schema-test-suite-data/

Expand Down
129 changes: 72 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,72 +41,77 @@ JsonValue value = Json.parse(json);

// Access as map-like structure
JsonObject obj = (JsonObject) value;
String name = ((JsonString) obj.members().get("name")).value();
int age = ((JsonNumber) obj.members().get("age")).intValue();
boolean active = ((JsonBoolean) obj.members().get("active")).value();
String name = ((JsonString) obj.members().get("name")).string();
long age = ((JsonNumber) obj.members().get("age")).toLong();
boolean active = ((JsonBoolean) obj.members().get("active")).bool();
```

### Simple Record Mapping

```java
// Define records for structured data
record User(String name, int age, boolean active) {}
record User(String name, long age, boolean active) {}

// Parse JSON directly to records
String userJson = "{\"name\":\"Bob\",\"age\":25,\"active\":false}";
JsonObject jsonObj = (JsonObject) Json.parse(userJson);

// Map to record
User user = new User(
((JsonString) jsonObj.members().get("name")).value(),
((JsonNumber) jsonObj.members().get("age")).intValue(),
((JsonBoolean) jsonObj.members().get("active")).value()
((JsonString) jsonObj.members().get("name")).string(),
((JsonNumber) jsonObj.members().get("age")).toLong(),
((JsonBoolean) jsonObj.members().get("active")).bool()
);

// Convert records back to JSON
JsonValue backToJson = Json.fromUntyped(Map.of(
"name", user.name(),
"age", user.age(),
"active", user.active()
// Convert records back to JSON using typed factories
JsonValue backToJson = JsonObject.of(Map.of(
"name", JsonString.of(user.name()),
"age", JsonNumber.of(user.age()),
"active", JsonBoolean.of(user.active())
));

// Covert back to a JSON string
// Convert back to a JSON string
String jsonString = backToJson.toString();
```

### Converting from Java Objects to JSON (`fromUntyped`)
### Building JSON Programmatically

```java
// Convert standard Java collections to JsonValue
Map<String, Object> data = Map.of(
"name", "John",
"age", 30,
"scores", List.of(85, 92, 78)
);
JsonValue json = Json.fromUntyped(data);
// Build JSON using typed factory methods
JsonObject data = JsonObject.of(Map.of(
"name", JsonString.of("John"),
"age", JsonNumber.of(30),
"scores", JsonArray.of(List.of(
JsonNumber.of(85),
JsonNumber.of(92),
JsonNumber.of(78)
))
));
String json = data.toString();
```

### Converting from JSON to Java Objects (`toUntyped`)
### Extracting Values from JSON

```java
// Convert JsonValue back to standard Java types
// Extract values from parsed JSON
JsonValue parsed = Json.parse("{\"name\":\"John\",\"age\":30}");
Object data = Json.toUntyped(parsed);
// Returns a Map<String, Object> with standard Java types
```
JsonObject obj = (JsonObject) parsed;

The conversion mappings are:
- `JsonObject` ↔ `Map<String, Object>`
- `JsonArray` ↔ `List<Object>`
- `JsonString` ↔ `String`
- `JsonNumber` ↔ `Number` (Long, Double, BigInteger, or BigDecimal)
- `JsonBoolean` ↔ `Boolean`
- `JsonNull` ↔ `null`
// Use the new type-safe accessor methods
String name = obj.get("name").string(); // Returns "John"
long age = obj.get("age").toLong(); // Returns 30L
double ageDouble = obj.get("age").toDouble(); // Returns 30.0
```

This is useful for:
- Integrating with existing code that uses standard collections
- Serializing/deserializing to formats that expect Java types
- Working with frameworks that use reflection on standard types
The accessor methods on `JsonValue`:
- `string()` - Returns the String value (for JsonString)
- `toLong()` - Returns the long value (for JsonNumber, if representable)
- `toDouble()` - Returns the double value (for JsonNumber, if representable)
- `bool()` - Returns the boolean value (for JsonBoolean)
- `elements()` - Returns List<JsonValue> (for JsonArray)
- `members()` - Returns Map<String, JsonValue> (for JsonObject)
- `get(String name)` - Access JsonObject member by name
- `element(int index)` - Access JsonArray element by index

### Realistic Record Mapping

Expand All @@ -123,29 +128,29 @@ Team team = new Team("Engineering", List.of(
new User("Bob", "bob@example.com", false)
));

// Convert records to JSON
JsonValue teamJson = Json.fromUntyped(Map.of(
"teamName", team.teamName(),
"members", team.members().stream()
.map(u -> Map.of(
"name", u.name(),
"email", u.email(),
"active", u.active()
))
.toList()
// Convert records to JSON using typed factories
JsonValue teamJson = JsonObject.of(Map.of(
"teamName", JsonString.of(team.teamName()),
"members", JsonArray.of(team.members().stream()
.map(u -> JsonObject.of(Map.of(
"name", JsonString.of(u.name()),
"email", JsonString.of(u.email()),
"active", JsonBoolean.of(u.active())
)))
.toList())
));

// Parse JSON back to records
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
Team reconstructed = new Team(
((JsonString) parsed.members().get("teamName")).value(),
((JsonArray) parsed.members().get("members")).values().stream()
((JsonString) parsed.members().get("teamName")).string(),
((JsonArray) parsed.members().get("members")).elements().stream()
.map(v -> {
JsonObject member = (JsonObject) v;
return new User(
((JsonString) member.members().get("name")).value(),
((JsonString) member.members().get("email")).value(),
((JsonBoolean) member.members().get("active")).value()
((JsonString) member.members().get("name")).string(),
((JsonString) member.members().get("email")).string(),
((JsonBoolean) member.members().get("active")).bool()
);
})
.toList()
Expand Down Expand Up @@ -182,10 +187,10 @@ Process JSON arrays efficiently with Java streams:
```java
// Filter active users from a JSON array
JsonArray users = (JsonArray) Json.parse(jsonArrayString);
List<String> activeUserEmails = users.values().stream()
List<String> activeUserEmails = users.elements().stream()
.map(v -> (JsonObject) v)
.filter(obj -> ((JsonBoolean) obj.members().get("active")).value())
.map(obj -> ((JsonString) obj.members().get("email")).value())
.filter(obj -> ((JsonBoolean) obj.members().get("active")).bool())
.map(obj -> ((JsonString) obj.members().get("email")).string())
.toList();
```

Expand Down Expand Up @@ -263,7 +268,14 @@ mvn exec:java -pl json-compatibility-suite -Dexec.args="--json"

## Current Status

This code (as of 2025-09-04) is derived from the OpenJDK jdk-sandbox repository “json” branch at commit [a8e7de8b49e4e4178eb53c94ead2fa2846c30635](https://github.com/openjdk/jdk-sandbox/commit/a8e7de8b49e4e4178eb53c94ead2fa2846c30635) ("Produce path/col during path building", 2025-08-14 UTC).
This code (as of 2026-01-25) is derived from the OpenJDK jdk-sandbox repository "json" branch. Key API changes from the previous version include:
- `JsonString.value()` → `JsonString.string()`
- `JsonNumber.toNumber()` → `JsonNumber.toLong()` / `JsonNumber.toDouble()`
- `JsonBoolean.value()` → `JsonBoolean.bool()`
- `JsonArray.values()` → `JsonArray.elements()`
- `Json.fromUntyped()` and `Json.toUntyped()` have been removed
- New accessor methods on `JsonValue`: `get(String)`, `element(int)`, `getOrAbsent(String)`, `valueOrNull()`
- Internal implementation changed from `StableValue` to `LazyConstant`

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)

Expand All @@ -276,8 +288,11 @@ A daily workflow runs an API comparison against the OpenJDK sandbox and prints a
## Modifications

This is a simplified backport with the following changes from the original:
- Replaced `StableValue.of()` with double-checked locking pattern.
- Replaced `LazyConstant` with a package-local polyfill using double-checked locking pattern.
- Added `Utils.powExact()` polyfill for `Math.powExact(long, int)` which is not available in Java 21.
- Replaced unnamed variables `_` with `ignored` for Java 21 compatibility.
- Removed `@ValueBased` annotations.
- Removed `@PreviewFeature` annotations.
- Compatible with JDK 21.

## Security Considerations
Expand Down
Loading