Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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=497
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