import jdk.sandbox.java.util.json.*;
+ import jdk.incubator.java.util.json.*;
JsonObject obj = (JsonObject) Json.parse("{\"name\":\"Alice\",\"age\":30}");
@@ -199,7 +199,7 @@ What’s Included
Record Mapping (explicit, typed)
- import jdk.sandbox.java.util.json.*;
+ import jdk.incubator.java.util.json.*;
import java.util.*;
record User(String name, String email, boolean active) {}
@@ -224,7 +224,7 @@ Record Mapping (explicit, typed)
Run the README Examples
mvn package
java -cp ./json-java21/target/java.util.json-*.jar:./json-java21/target/test-classes \
- jdk.sandbox.java.util.json.examples.ReadmeExamples
+ jdk.incubator.java.util.json.examples.ReadmeExamples
JSON Test Suite Compatibility
@@ -237,7 +237,7 @@ JSON Test Suite Compatibility
JSON Type Definition (JTD) Validator
import json.java21.jtd.Jtd;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
JsonValue schema = Json.parse("""
{
diff --git a/jdt2jar/src/main/java/json/java21/jdt2jar/Jdt2Jar.java b/jdt2jar/src/main/java/json/java21/jdt2jar/Jdt2Jar.java
index 364f8d80..232ef459 100644
--- a/jdt2jar/src/main/java/json/java21/jdt2jar/Jdt2Jar.java
+++ b/jdt2jar/src/main/java/json/java21/jdt2jar/Jdt2Jar.java
@@ -1,6 +1,6 @@
package json.java21.jdt2jar;
-import jdk.sandbox.java.util.json.Json;
+import jdk.incubator.java.util.json.Json;
import json.java21.jtd.codegen.JtdCodegen;
import java.io.ByteArrayOutputStream;
@@ -158,8 +158,8 @@ private static void copyJarEntries(JarOutputStream out, Set written, Pat
}
private static boolean shouldCopyRuntime(String path) {
- return (path.startsWith("jdk/sandbox/java/util/json/")
- || path.startsWith("jdk/sandbox/internal/util/json/")
+ return (path.startsWith("jdk/incubator/java/util/json/")
+ || path.startsWith("jdk/incubator/internal/util/json/")
|| path.startsWith("json/java21/jtd/"))
&& !path.startsWith("json/java21/jtd/codegen/")
|| path.startsWith("json/java21/jtd/codegen/JtdValidator.class")
@@ -202,7 +202,7 @@ private static void writeSourceFile(Path sourcePath, Options options) throws IOE
final var source = """
package %s;
- import jdk.sandbox.java.util.json.JsonValue;
+ import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.JtdValidationResult;
import json.java21.jdt2jar.runtime.ValidatorMain;
diff --git a/jdt2jar/src/main/java/json/java21/jdt2jar/runtime/ValidatorMain.java b/jdt2jar/src/main/java/json/java21/jdt2jar/runtime/ValidatorMain.java
index fe9db373..c2af0fba 100644
--- a/jdt2jar/src/main/java/json/java21/jdt2jar/runtime/ValidatorMain.java
+++ b/jdt2jar/src/main/java/json/java21/jdt2jar/runtime/ValidatorMain.java
@@ -1,10 +1,10 @@
package json.java21.jdt2jar.runtime;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonParseException;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.JtdValidationResult;
import java.io.IOException;
@@ -120,8 +120,8 @@ private static String readResourceString(String name) throws IOException {
private static JsonObject toJson(JtdValidationResult result) {
return JsonObject.of(java.util.Map.of(
- "valid", jdk.sandbox.java.util.json.JsonBoolean.of(result.isValid()),
- "errors", jdk.sandbox.java.util.json.JsonArray.of(result.errors().stream()
+ "valid", jdk.incubator.java.util.json.JsonBoolean.of(result.isValid()),
+ "errors", jdk.incubator.java.util.json.JsonArray.of(result.errors().stream()
.map(error -> JsonObject.of(java.util.Map.of(
"instancePath", JsonString.of(error.instancePath()),
"schemaPath", JsonString.of(error.schemaPath()))))
diff --git a/json-compatibility-suite/pom.xml b/json-compatibility-suite/pom.xml
index e834484f..cfbfc5e4 100644
--- a/json-compatibility-suite/pom.xml
+++ b/json-compatibility-suite/pom.xml
@@ -73,7 +73,7 @@
exec-maven-plugin
3.4.1
- jdk.sandbox.compatibility.JsonCompatibilitySummary
+ jdk.incubator.compatibility.JsonCompatibilitySummary
false
diff --git a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
similarity index 97%
rename from json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java
rename to json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
index 36614543..3557ab54 100644
--- a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java
+++ b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
@@ -1,11 +1,11 @@
-package jdk.sandbox.compatibility;
+package jdk.incubator.compatibility;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonParseException;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
diff --git a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/RobustCharDecoder.java
similarity index 99%
rename from json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java
rename to json-compatibility-suite/src/main/java/jdk/incubator/compatibility/RobustCharDecoder.java
index deb46078..d719f4ac 100644
--- a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/RobustCharDecoder.java
+++ b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/RobustCharDecoder.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.compatibility;
+package jdk.incubator.compatibility;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
diff --git a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java b/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
similarity index 97%
rename from json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java
rename to json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
index 5edede1b..96b05557 100644
--- a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java
+++ b/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.compatibility;
+package jdk.incubator.compatibility;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
index 49e69eae..05a17ef9 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
@@ -1,11 +1,11 @@
package io.github.simbo1905.tracker;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonBoolean;
import java.io.IOException;
import java.net.URI;
@@ -97,14 +97,14 @@ static String fetchFromUrl(String url) {
}
/// Discovers all classes in the local JSON API packages
- /// @return sorted set of classes from jdk.sandbox.java.util.json and jdk.sandbox.internal.util.json
+ /// @return sorted set of classes from jdk.incubator.java.util.json and jdk.incubator.internal.util.json
static Set> discoverLocalJsonClasses() {
LOGGER.info("Starting class discovery for JSON API packages");
final var classes = new TreeSet>(Comparator.comparing(Class::getName));
// Packages to scan - only public API, not internal implementation
final var packages = List.of(
- "jdk.sandbox.java.util.json"
+ "jdk.incubator.java.util.json"
);
final var classLoader = Thread.currentThread().getContextClassLoader();
@@ -225,7 +225,7 @@ static Map fetchUpstreamSources(Set> localClasses) {
continue;
}
- // Map package name from jdk.sandbox.* to standard java.*
+ // Map package name from jdk.incubator.* to standard java.*
final var upstreamPath = mapToUpstreamPath(className);
final var url = GITHUB_BASE_URL + upstreamPath;
@@ -266,10 +266,10 @@ static Map fetchUpstreamSources(Set> localClasses) {
/// Maps local class name to upstream GitHub path
static String mapToUpstreamPath(String className) {
- // Remove jdk.sandbox prefix and map to the jdk.incubator.json module packages
+ // Remove jdk.incubator prefix and map to the jdk.incubator.json module packages
String path = className
- .replace("jdk.sandbox.java.util.json", "jdk/incubator/json")
- .replace("jdk.sandbox.internal.util.json", "jdk/incubator/json/impl")
+ .replace("jdk.incubator.java.util.json", "jdk/incubator/json")
+ .replace("jdk.incubator.internal.util.json", "jdk/incubator/json/impl")
.replace('.', '/');
return path + ".java";
@@ -865,9 +865,9 @@ static String normalizeTypeName(String typeName) {
// Handle generic types
var normalized = typeName;
- // Replace jdk.sandbox.* with the upstream incubator packages
- normalized = normalized.replace("jdk.sandbox.java.util.json", "jdk.incubator.json");
- normalized = normalized.replace("jdk.sandbox.internal.util.json", "jdk.incubator.json.impl");
+ // Replace jdk.incubator.* with the upstream incubator packages
+ normalized = normalized.replace("jdk.incubator.java.util.json", "jdk.incubator.json");
+ normalized = normalized.replace("jdk.incubator.internal.util.json", "jdk.incubator.json.impl");
// Remove any remaining package prefixes for comparison
if (normalized.contains(".")) {
@@ -886,7 +886,7 @@ static JsonObject runFullComparison() {
final var reportMap = new LinkedHashMap();
reportMap.put("timestamp", JsonString.of(startTime.toString()));
- reportMap.put("localPackage", JsonString.of("jdk.sandbox.java.util.json"));
+ reportMap.put("localPackage", JsonString.of("jdk.incubator.java.util.json"));
reportMap.put("upstreamPackage", JsonString.of("jdk.incubator.json"));
// Discover local classes
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
index 866cd2a3..d71aec8e 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
@@ -1,6 +1,6 @@
package io.github.simbo1905.tracker;
-import jdk.sandbox.java.util.json.Json;
+import jdk.incubator.java.util.json.Json;
import java.io.IOException;
import java.nio.file.Files;
@@ -31,7 +31,7 @@ public static void main(String[] args) {
configureLogging(logLevel);
System.out.println("=== JSON API Tracker ===");
- System.out.println("Comparing local jdk.sandbox.java.util.json with upstream jdk.incubator.json");
+ System.out.println("Comparing local jdk.incubator.java.util.json with upstream jdk.incubator.json");
System.out.println("Log level: " + logLevel);
System.out.println("Mode: " + mode);
if (sourcePath != null) {
diff --git a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
index 82cd6a8c..820de79a 100644
--- a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
+++ b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
@@ -7,10 +7,10 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import jdk.sandbox.java.util.json.JsonBoolean;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
import java.util.Set;
import java.util.Map;
@@ -37,17 +37,17 @@ void testDiscoverLocalJsonClasses() {
// Should find core JSON interfaces
assertThat(classes.stream().map(Class::getName))
.contains(
- "jdk.sandbox.java.util.json.JsonValue",
- "jdk.sandbox.java.util.json.JsonObject",
- "jdk.sandbox.java.util.json.JsonArray",
- "jdk.sandbox.java.util.json.JsonString",
- "jdk.sandbox.java.util.json.JsonNumber",
- "jdk.sandbox.java.util.json.JsonBoolean",
- "jdk.sandbox.java.util.json.JsonNull"
+ "jdk.incubator.java.util.json.JsonValue",
+ "jdk.incubator.java.util.json.JsonObject",
+ "jdk.incubator.java.util.json.JsonArray",
+ "jdk.incubator.java.util.json.JsonString",
+ "jdk.incubator.java.util.json.JsonNumber",
+ "jdk.incubator.java.util.json.JsonBoolean",
+ "jdk.incubator.java.util.json.JsonNull"
);
// Should NOT find internal implementation classes (public API only)
- assertThat(classes.stream().anyMatch(c -> c.getName().startsWith("jdk.sandbox.internal.util.json")))
+ assertThat(classes.stream().anyMatch(c -> c.getName().startsWith("jdk.incubator.internal.util.json")))
.as("Should not find internal implementation classes - public API only")
.isFalse();
@@ -65,7 +65,7 @@ class LocalApiExtractionTests {
@Test
@DisplayName("Should extract API from JsonObject interface source")
void testExtractLocalApiJsonObject() {
- final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.JsonObject");
+ final var api = ApiTracker.extractLocalApiFromSource("jdk.incubator.java.util.json.JsonObject");
assertThat(api).isNotNull();
// Check if extraction succeeded or failed
@@ -79,7 +79,7 @@ void testExtractLocalApiJsonObject() {
assertThat(((JsonString) api.members().get("className")).string()).isEqualTo("JsonObject");
assertThat(api.members()).containsKey("packageName");
- assertThat(((JsonString) api.members().get("packageName")).string()).isEqualTo("jdk.sandbox.java.util.json");
+ assertThat(((JsonString) api.members().get("packageName")).string()).isEqualTo("jdk.incubator.java.util.json");
assertThat(api.members()).containsKey("isInterface");
assertThat(api.members().get("isInterface")).isEqualTo(JsonBoolean.of(true));
@@ -89,7 +89,7 @@ void testExtractLocalApiJsonObject() {
@Test
@DisplayName("Should extract API from JsonValue sealed interface source")
void testExtractLocalApiJsonValue() {
- final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.JsonValue");
+ final var api = ApiTracker.extractLocalApiFromSource("jdk.incubator.java.util.json.JsonValue");
// Check if extraction succeeded or failed
if (api.members().containsKey("error")) {
@@ -111,7 +111,7 @@ void testExtractLocalApiJsonValue() {
@Test
@DisplayName("Should handle missing source file gracefully")
void testExtractLocalApiMissingFile() {
- final var api = ApiTracker.extractLocalApiFromSource("jdk.sandbox.java.util.json.NonExistentClass");
+ final var api = ApiTracker.extractLocalApiFromSource("jdk.incubator.java.util.json.NonExistentClass");
assertThat(api.members()).containsKey("error");
final var error = ((JsonString) api.members().get("error")).string();
@@ -126,10 +126,10 @@ class UpstreamFetchingTests {
@Test
@DisplayName("Should map local class names to upstream paths")
void testMapToUpstreamPath() {
- assertThat(ApiTracker.mapToUpstreamPath("jdk.sandbox.java.util.json.JsonObject"))
+ assertThat(ApiTracker.mapToUpstreamPath("jdk.incubator.java.util.json.JsonObject"))
.isEqualTo("jdk/incubator/json/JsonObject.java");
- assertThat(ApiTracker.mapToUpstreamPath("jdk.sandbox.internal.util.json.JsonObjectImpl"))
+ assertThat(ApiTracker.mapToUpstreamPath("jdk.incubator.internal.util.json.JsonObjectImpl"))
.isEqualTo("jdk/incubator/json/impl/JsonObjectImpl.java");
}
@@ -224,7 +224,7 @@ class TypeNameNormalizationTests {
@Test
@DisplayName("Should normalize type names correctly")
void testNormalizeTypeName() {
- assertThat(ApiTracker.normalizeTypeName("jdk.sandbox.java.util.json.JsonValue"))
+ assertThat(ApiTracker.normalizeTypeName("jdk.incubator.java.util.json.JsonValue"))
.isEqualTo("JsonValue");
assertThat(ApiTracker.normalizeTypeName("java.lang.String"))
diff --git a/json-java21-jsonpath/README.md b/json-java21-jsonpath/README.md
index f38d36ff..5c2af453 100644
--- a/json-java21-jsonpath/README.md
+++ b/json-java21-jsonpath/README.md
@@ -1,6 +1,6 @@
# JsonPath
-This module provides a JSONPath-style query engine for JSON documents parsed with `jdk.sandbox.java.util.json`.
+This module provides a JSONPath-style query engine for JSON documents parsed with `jdk.incubator.java.util.json`.
It is based on the original Stefan Goessner JSONPath article:
https://goessner.net/articles/JsonPath/
@@ -8,7 +8,7 @@ https://goessner.net/articles/JsonPath/
## Quick Start
```java
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import json.java21.jsonpath.JsonPath;
JsonValue doc = Json.parse("""
diff --git a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
index a18225aa..58b1d831 100644
--- a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
+++ b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
@@ -1,6 +1,6 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import java.util.ArrayList;
import java.util.List;
diff --git a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
index 28e0f61b..59221e40 100644
--- a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
+++ b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
@@ -1,6 +1,6 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
/// Helpers for stream-based processing of `JsonPath.query(...)` results.
public final class JsonPathStreams {
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/FunctionsReadmeDemo.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/FunctionsReadmeDemo.java
index 05842dd1..ac579613 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/FunctionsReadmeDemo.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/FunctionsReadmeDemo.java
@@ -1,6 +1,6 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import org.junit.jupiter.api.Test;
import java.util.List;
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
index 54db45d2..1b1c4b2c 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
@@ -1,7 +1,7 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
@@ -158,7 +158,7 @@ void testComplexNestedLogic() {
// Helper to extract integer field for assertions
private int asInt(JsonValue v, @SuppressWarnings("SameParameterValue") String key) {
- if (v instanceof jdk.sandbox.java.util.json.JsonObject obj) {
+ if (v instanceof jdk.incubator.java.util.json.JsonObject obj) {
return (int) obj.members().get(key).toLong();
}
throw new IllegalArgumentException("Not an object");
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
index 3fcd3a80..0d0645a5 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
@@ -1,6 +1,6 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
index a84fa266..d645b10b 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
@@ -1,7 +1,7 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathStreamsTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathStreamsTest.java
index edb0672f..844c877a 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathStreamsTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathStreamsTest.java
@@ -1,6 +1,6 @@
package json.java21.jsonpath;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
diff --git a/json-java21-jtd-codegen/README.md b/json-java21-jtd-codegen/README.md
index e85c5a22..0036a123 100644
--- a/json-java21-jtd-codegen/README.md
+++ b/json-java21-jtd-codegen/README.md
@@ -26,7 +26,7 @@ For **infrequent validation** (config loading, startup checks, one-off validatio
## Usage
```java
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import json.java21.jtd.codegen.JtdValidator;
JsonValue schema = Json.parse("""
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/Descriptors.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/Descriptors.java
index fa1317fa..f2d3e9e4 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/Descriptors.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/Descriptors.java
@@ -31,13 +31,13 @@ private Descriptors() {}
static final ClassDesc CD_Iterator = ClassDesc.of("java.util.Iterator");
// -- JSON API types --
- static final ClassDesc CD_JsonValue = ClassDesc.of("jdk.sandbox.java.util.json.JsonValue");
- static final ClassDesc CD_JsonObject = ClassDesc.of("jdk.sandbox.java.util.json.JsonObject");
- static final ClassDesc CD_JsonArray = ClassDesc.of("jdk.sandbox.java.util.json.JsonArray");
- static final ClassDesc CD_JsonString = ClassDesc.of("jdk.sandbox.java.util.json.JsonString");
- static final ClassDesc CD_JsonNumber = ClassDesc.of("jdk.sandbox.java.util.json.JsonNumber");
- static final ClassDesc CD_JsonBoolean = ClassDesc.of("jdk.sandbox.java.util.json.JsonBoolean");
- static final ClassDesc CD_JsonNull = ClassDesc.of("jdk.sandbox.java.util.json.JsonNull");
+ static final ClassDesc CD_JsonValue = ClassDesc.of("jdk.incubator.java.util.json.JsonValue");
+ static final ClassDesc CD_JsonObject = ClassDesc.of("jdk.incubator.java.util.json.JsonObject");
+ static final ClassDesc CD_JsonArray = ClassDesc.of("jdk.incubator.java.util.json.JsonArray");
+ static final ClassDesc CD_JsonString = ClassDesc.of("jdk.incubator.java.util.json.JsonString");
+ static final ClassDesc CD_JsonNumber = ClassDesc.of("jdk.incubator.java.util.json.JsonNumber");
+ static final ClassDesc CD_JsonBoolean = ClassDesc.of("jdk.incubator.java.util.json.JsonBoolean");
+ static final ClassDesc CD_JsonNull = ClassDesc.of("jdk.incubator.java.util.json.JsonNull");
// -- Validation result types --
static final ClassDesc CD_JtdValidationError = ClassDesc.of("json.java21.jtd.JtdValidationError");
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
index 4697ea0c..609432d9 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
@@ -9,7 +9,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.Jtd;
/// Compiles a JTD schema into a bytecode-generated [JtdValidator].
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdValidator.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdValidator.java
index 26c61377..5310c94a 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdValidator.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdValidator.java
@@ -1,6 +1,6 @@
package json.java21.jtd.codegen;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.JtdValidationResult;
import java.util.Objects;
diff --git a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/BenchmarkTest.java b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/BenchmarkTest.java
index e8f34b86..ee1c953f 100644
--- a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/BenchmarkTest.java
+++ b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/BenchmarkTest.java
@@ -1,7 +1,7 @@
package json.java21.jtd.codegen;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap;
diff --git a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
index 17a4870d..f5079efc 100644
--- a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
+++ b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
@@ -1,10 +1,10 @@
package json.java21.jtd.codegen;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.JtdValidationError;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
diff --git a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CrossValidationTest.java b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CrossValidationTest.java
index 1d003927..950ed8ff 100644
--- a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CrossValidationTest.java
+++ b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CrossValidationTest.java
@@ -1,6 +1,6 @@
package json.java21.jtd.codegen;
-import jdk.sandbox.java.util.json.Json;
+import jdk.incubator.java.util.json.Json;
import json.java21.jtd.JtdValidationError;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
diff --git a/json-java21-jtd/ARCHITECTURE.md b/json-java21-jtd/ARCHITECTURE.md
index edf0e83c..2e9aea4e 100644
--- a/json-java21-jtd/ARCHITECTURE.md
+++ b/json-java21-jtd/ARCHITECTURE.md
@@ -60,7 +60,7 @@ Following modern Java patterns, we use a package-private sealed interface with r
```java
package json.java21.jtd;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
/// Package-private sealed interface for schema types
sealed interface JtdSchema
@@ -250,7 +250,7 @@ record CompiledSchema(
## Usage Example
```java
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import json.java21.jtd.Jtd;
// Create JTD validator
diff --git a/json-java21-jtd/README.md b/json-java21-jtd/README.md
index 56c2472d..ac7c11ec 100644
--- a/json-java21-jtd/README.md
+++ b/json-java21-jtd/README.md
@@ -23,7 +23,7 @@ For **repeated hot-path validation** (e.g., event processing, API gateways), con
```java
import json.java21.jtd.Jtd;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
// Create a JTD schema
String schemaJson = """
@@ -202,7 +202,7 @@ A schema can be compiled into a reusable `JtdValidator` -- a functional interfac
```java
import json.java21.jtd.JtdValidator;
import json.java21.jtd.JtdValidationResult;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
String schemaJson = """
{ "type": "string" }
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/Frame.java b/json-java21-jtd/src/main/java/json/java21/jtd/Frame.java
index 07d098d4..c7587306 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/Frame.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/Frame.java
@@ -1,6 +1,6 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonValue;
/// Stack frame for iterative validation with path and offset tracking.
///
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java b/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
index eaaea632..760cfc5e 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
@@ -1,6 +1,6 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonValue;
import java.util.ArrayList;
import java.util.List;
@@ -53,7 +53,7 @@ private void stepRfc8927(Frame frame, java.util.Deque stack, List stack, List errors) {
final var instance = frame.instance();
final var ok = switch (type.type()) {
- case "boolean" -> instance instanceof jdk.sandbox.java.util.json.JsonBoolean;
- case "string" -> instance instanceof jdk.sandbox.java.util.json.JsonString;
+ case "boolean" -> instance instanceof jdk.incubator.java.util.json.JsonBoolean;
+ case "string" -> instance instanceof jdk.incubator.java.util.json.JsonString;
case "timestamp" -> isTimestamp(instance);
- case "float32", "float64" -> instance instanceof jdk.sandbox.java.util.json.JsonNumber;
+ case "float32", "float64" -> instance instanceof jdk.incubator.java.util.json.JsonNumber;
case "int8" -> isIntInRange(instance, -128, 127);
case "uint8" -> isIntInRange(instance, 0, 255);
case "int16" -> isIntInRange(instance, -32768, 32767);
@@ -97,7 +97,7 @@ private void stepType(Frame frame, JtdSchema.TypeSchema type, List errors) {
- if (!(frame.instance() instanceof jdk.sandbox.java.util.json.JsonString str)
+ if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonString str)
|| !enumS.values().contains(str.string())) {
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + "/enum"));
}
@@ -105,7 +105,7 @@ private void stepEnum(Frame frame, JtdSchema.EnumSchema enumS, List stack, List errors) {
- if (!(frame.instance() instanceof jdk.sandbox.java.util.json.JsonArray arr)) {
+ if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonArray arr)) {
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + "/elements"));
return;
}
@@ -122,7 +122,7 @@ private void stepElements(Frame frame, JtdSchema.ElementsSchema elems,
private void stepProperties(Frame frame, JtdSchema.PropertiesSchema props,
java.util.Deque stack, List errors) {
- if (!(frame.instance() instanceof jdk.sandbox.java.util.json.JsonObject obj)) {
+ if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonObject obj)) {
final var guardPath = props.properties().isEmpty() ? "/optionalProperties" : "/properties";
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + guardPath));
return;
@@ -176,7 +176,7 @@ private void stepProperties(Frame frame, JtdSchema.PropertiesSchema props,
private void stepValues(Frame frame, JtdSchema.ValuesSchema vals,
java.util.Deque stack, List errors) {
- if (!(frame.instance() instanceof jdk.sandbox.java.util.json.JsonObject obj)) {
+ if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonObject obj)) {
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + "/values"));
return;
}
@@ -191,7 +191,7 @@ private void stepValues(Frame frame, JtdSchema.ValuesSchema vals,
private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
java.util.Deque stack, List errors) {
- if (!(frame.instance() instanceof jdk.sandbox.java.util.json.JsonObject obj)) {
+ if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonObject obj)) {
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + "/discriminator"));
return;
}
@@ -205,7 +205,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
}
final var tagValue = members.get(disc.discriminator());
- if (!(tagValue instanceof jdk.sandbox.java.util.json.JsonString tagStr)) {
+ if (!(tagValue instanceof jdk.incubator.java.util.json.JsonString tagStr)) {
errors.add(new JtdValidationError(
frame.ptr() + "/" + disc.discriminator(),
sp + "/discriminator"));
@@ -231,7 +231,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
// ------------------------------------------------------------------
private static boolean isTimestamp(JsonValue instance) {
- if (!(instance instanceof jdk.sandbox.java.util.json.JsonString str)) return false;
+ if (!(instance instanceof jdk.incubator.java.util.json.JsonString str)) return false;
final var value = str.string();
if (!JtdSchema.TypeSchema.RFC3339.matcher(value).matches()) return false;
try {
@@ -244,7 +244,7 @@ private static boolean isTimestamp(JsonValue instance) {
}
private static boolean isIntInRange(JsonValue instance, long min, long max) {
- if (!(instance instanceof jdk.sandbox.java.util.json.JsonNumber num)) return false;
+ if (!(instance instanceof jdk.incubator.java.util.json.JsonNumber num)) return false;
final var d = num.toDouble();
if (d != Math.floor(d)) return false;
if (d > Long.MAX_VALUE || d < Long.MIN_VALUE) return false;
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java b/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
index 7c8c4d67..03e217e6 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.*;
-import jdk.sandbox.internal.util.json.*;
+import jdk.incubator.java.util.json.*;
+import jdk.incubator.internal.util.json.*;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java b/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
index fcfc8ff9..1963d3ac 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
@@ -1,6 +1,6 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/JtdValidator.java b/json-java21-jtd/src/main/java/json/java21/jtd/JtdValidator.java
index 829f853a..8adfad50 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/JtdValidator.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/JtdValidator.java
@@ -1,6 +1,6 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonValue;
import java.util.Objects;
import java.util.logging.Logger;
diff --git a/json-java21-jtd/src/test/java/json/java21/jdt/demo/VisibilityTest.java b/json-java21-jtd/src/test/java/json/java21/jdt/demo/VisibilityTest.java
index 57b0cffe..7e32bb82 100644
--- a/json-java21-jtd/src/test/java/json/java21/jdt/demo/VisibilityTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jdt/demo/VisibilityTest.java
@@ -1,7 +1,7 @@
package json.java21.jdt.demo;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.Jtd;
import json.java21.jtd.JtdTestBase;
import org.junit.jupiter.api.Test;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/CompilerSpecIT.java b/json-java21-jtd/src/test/java/json/java21/jtd/CompilerSpecIT.java
index a96abf6c..08ed9d6f 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/CompilerSpecIT.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/CompilerSpecIT.java
@@ -2,8 +2,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/CompilerTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/CompilerTest.java
index dea21ba9..2e13b333 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/CompilerTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/CompilerTest.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/DiscriminatorEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/DiscriminatorEdgeCaseProbe.java
index 10080657..58402441 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/DiscriminatorEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/DiscriminatorEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/DocumentationAJvTests.java b/json-java21-jtd/src/test/java/json/java21/jtd/DocumentationAJvTests.java
index 162c1fcb..9feda347 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/DocumentationAJvTests.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/DocumentationAJvTests.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/ElementsEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/ElementsEdgeCaseProbe.java
index 9008ce3b..5771c7ee 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/ElementsEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/ElementsEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.List;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/ErrorFormatComplianceProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/ErrorFormatComplianceProbe.java
index de61e659..ed61b28b 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/ErrorFormatComplianceProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/ErrorFormatComplianceProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.List;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
index 12a325a7..708a2d15 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
@@ -1,6 +1,6 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import net.jqwik.api.*;
import org.junit.jupiter.api.Assertions;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
index 667d88a7..722c3cf6 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
@@ -1,10 +1,10 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecIT.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecIT.java
index 20fbac4b..d9258f49 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecIT.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecIT.java
@@ -2,8 +2,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
index be49bc77..e669f58f 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
index cecc32cb..e23dae60 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/PropertiesEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/PropertiesEdgeCaseProbe.java
index 5c555359..05f2203c 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/PropertiesEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/PropertiesEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
index e1f6b970..46e9fc48 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.List;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
index 9166f6c3..e582db3d 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
@@ -1,8 +1,8 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
-import jdk.sandbox.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonNumber;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927Compliance.java b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927Compliance.java
index 3c522c45..eb3d468e 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927Compliance.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927Compliance.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TestValidationErrors.java b/json-java21-jtd/src/test/java/json/java21/jtd/TestValidationErrors.java
index 33757878..3ac273be 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TestValidationErrors.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TestValidationErrors.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
index 265adca4..1f0e24cc 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
@@ -1,7 +1,7 @@
package json.java21.jtd;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21/AGENTS.md b/json-java21/AGENTS.md
index 6269a0c1..3cb67150 100644
--- a/json-java21/AGENTS.md
+++ b/json-java21/AGENTS.md
@@ -77,16 +77,16 @@ wc -l .tmp/upstream-sync/jdk/internal/util/json/*.java
Create parallel structure in `.tmp/backported/` with our package names:
```bash
-mkdir -p .tmp/backported/jdk/sandbox/java/util/json
-mkdir -p .tmp/backported/jdk/sandbox/internal/util/json
+mkdir -p .tmp/backported/jdk/incubator/java/util/json
+mkdir -p .tmp/backported/jdk/incubator/internal/util/json
```
### Step 4: Apply Backporting Transformations
For each downloaded file, apply these transformations using Python heredocs (not sed/perl for multi-line):
#### 4.1 Package Renaming
-- `java.util.json` → `jdk.sandbox.java.util.json`
-- `jdk.internal.util.json` → `jdk.sandbox.internal.util.json`
+- `java.util.json` → `jdk.incubator.java.util.json`
+- `jdk.internal.util.json` → `jdk.incubator.internal.util.json`
#### 4.2 Remove Preview Feature Annotations
Delete lines containing:
@@ -119,7 +119,7 @@ javadoc and `@Serial serialVersionUID` stripped; behaviour identical). Take the
with the standard transforms of 4.1/4.2; do not treat it as local-only.
#### 4.6 Preserve Demo File
-The file `jdk/sandbox/demo/JsonDemo.java` is a local addition for demonstration purposes. Preserve it. Fix it.
+The file `jdk/incubator/demo/JsonDemo.java` is a local addition for demonstration purposes. Preserve it. Fix it.
### Step 5: Verify Compilation with javac
Before copying to the main source tree, verify the backported code compiles:
@@ -129,7 +129,7 @@ Before copying to the main source tree, verify the backported code compiles:
find .tmp/backported -name "*.java" > .tmp/sources.txt
# Also include our polyfill
-echo "json-java21/src/main/java/jdk/sandbox/internal/util/json/LazyConstant.java" >> .tmp/sources.txt
+echo "json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java" >> .tmp/sources.txt
# Compile with Java 21
javac --release 21 -d .tmp/classes @.tmp/sources.txt
@@ -141,20 +141,20 @@ Only after javac succeeds:
```bash
# Backup current sources (optional)
-cp -r json-java21/src/main/java/jdk/sandbox .tmp/backup-sandbox
+cp -r json-java21/src/main/java/jdk/incubator .tmp/backup-incubator
# Copy backported files (excluding our local additions)
-cp .tmp/backported/jdk/sandbox/java/util/json/*.java \
- json-java21/src/main/java/jdk/sandbox/java/util/json/
+cp .tmp/backported/jdk/incubator/java/util/json/*.java \
+ json-java21/src/main/java/jdk/incubator/java/util/json/
-cp .tmp/backported/jdk/sandbox/internal/util/json/*.java \
- json-java21/src/main/java/jdk/sandbox/internal/util/json/
+cp .tmp/backported/jdk/incubator/internal/util/json/*.java \
+ json-java21/src/main/java/jdk/incubator/internal/util/json/
# Restore our local additions if overwritten
# (LazyConstant.java should not be in backported/)
```
-The file `jdk/sandbox/demo/JsonDemo.java` should be the example code in our README.md, as it may have changed to reflect upstream changes. You MUST update the README.md to include examples of the upgraded code in this file, which you must MANUALLY VERIFY IS GOOD post-upgrade.
+The file `jdk/incubator/demo/JsonDemo.java` should be the example code in our README.md, as it may have changed to reflect upstream changes. You MUST update the README.md to include examples of the upgraded code in this file, which you must MANUALLY VERIFY IS GOOD post-upgrade.
### Step 7: Full Maven Build
@@ -166,8 +166,8 @@ $(command -v mvnd || command -v mvn || command -v ./mvnw) clean test -pl json-ja
| File | Purpose |
|------|---------|
-| `jdk/sandbox/internal/util/json/LazyConstant.java` | Java 21 polyfill for the JDK `java.lang.LazyConstant` API used by upstream since `c1a4f80` |
-| `jdk/sandbox/demo/JsonDemo.java` | Demonstration/example code |
+| `jdk/incubator/internal/util/json/LazyConstant.java` | Java 21 polyfill for the JDK `java.lang.LazyConstant` API used by upstream since `c1a4f80` |
+| `jdk/incubator/demo/JsonDemo.java` | Demonstration/example code |
Note: `JsonAssertionException.java` is shipped upstream (see step 4.5) and
`StableValue.java` is unused legacy pending removal; neither is a local addition anymore.
@@ -188,9 +188,9 @@ public final class JsonStringImpl implements JsonString, JsonValueImpl {
**Backported version:**
```java
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonString;
// LazyConstant is package-local (our polyfill for java.lang.LazyConstant), no import needed
public final class JsonStringImpl implements JsonString, JsonValueImpl {
diff --git a/json-java21/src/main/java/jdk/sandbox/demo/JsonDemo.java b/json-java21/src/main/java/jdk/incubator/demo/JsonDemo.java
similarity index 71%
rename from json-java21/src/main/java/jdk/sandbox/demo/JsonDemo.java
rename to json-java21/src/main/java/jdk/incubator/demo/JsonDemo.java
index 1e501ad4..5703bb9d 100644
--- a/json-java21/src/main/java/jdk/sandbox/demo/JsonDemo.java
+++ b/json-java21/src/main/java/jdk/incubator/demo/JsonDemo.java
@@ -1,9 +1,9 @@
-package jdk.sandbox.demo;
+package jdk.incubator.demo;
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonNumber;
import java.util.Map;
public class JsonDemo {
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonArrayImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
similarity index 94%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonArrayImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
index ec7ef740..e5b1f224 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonArrayImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
@@ -23,13 +23,13 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.Collections;
import java.util.List;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonValue;
/**
* JsonArray implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonBooleanImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
similarity index 96%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonBooleanImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
index 9f421c0d..f9f9a6c3 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonBooleanImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
@@ -23,9 +23,9 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonBoolean;
/**
* JsonBoolean implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNullImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
similarity index 95%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNullImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
index f8852aff..b55ced05 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNullImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
@@ -23,9 +23,9 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNull;
/**
* JsonNull implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNumberImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNumberImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
index 8c2e2bc4..6a823829 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonNumberImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
@@ -23,12 +23,12 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.Locale;
import java.util.Optional;
-import jdk.sandbox.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonNumber;
/**
* JsonNumber implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonObjectImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
similarity index 95%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonObjectImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
index c42bd1a5..3906cadf 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonObjectImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
@@ -23,13 +23,13 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.Collections;
import java.util.Map;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonValue;
/**
* JsonObject implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonParser.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonParser.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
index b5261f3e..0342510f 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonParser.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
@@ -23,7 +23,7 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -31,11 +31,11 @@
import java.util.Map;
import java.util.function.Supplier;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonParseException;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
/**
* Parses a JSON Document char[] into a tree of JsonValues. JsonObject and JsonArray
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonStringImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonStringImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
index a7423e1f..c75559bb 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonStringImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
@@ -23,9 +23,9 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonString;
/**
* JsonString implementation class
*/
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonValueImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
similarity index 92%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonValueImpl.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
index a0827335..ab62a364 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/JsonValueImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
/**
* Used for JsonAssertionException error message building.
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/LazyConstant.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
similarity index 95%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/LazyConstant.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
index 71bdff97..ece6888d 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/LazyConstant.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.function.Supplier;
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
similarity index 97%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
index 1fafaa3b..1d3bdbf9 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/StableValue.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import java.util.function.Supplier;
diff --git a/json-java21/src/main/java/jdk/sandbox/internal/util/json/Utils.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
similarity index 95%
rename from json-java21/src/main/java/jdk/sandbox/internal/util/json/Utils.java
rename to json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
index d1160622..be50413d 100644
--- a/json-java21/src/main/java/jdk/sandbox/internal/util/json/Utils.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
@@ -23,16 +23,16 @@
* questions.
*/
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonAssertionException;
-import jdk.sandbox.java.util.json.JsonBoolean;
-import jdk.sandbox.java.util.json.JsonNull;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonAssertionException;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
/**
* Shared utilities for Json classes.
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/Json.java b/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/Json.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
index bc5f003f..77518a35 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/Json.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
@@ -22,7 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import java.util.ArrayList;
import java.util.Arrays;
@@ -32,8 +32,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
-import jdk.sandbox.internal.util.json.JsonParser;
-import jdk.sandbox.internal.util.json.Utils;
+import jdk.incubator.internal.util.json.JsonParser;
+import jdk.incubator.internal.util.json.Utils;
/**
* This class provides static methods for parsing and generating JSON documents
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonArray.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
similarity index 97%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonArray.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
index 614e4f57..c0e7d2de 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonArray.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
@@ -23,14 +23,14 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
-import jdk.sandbox.internal.util.json.JsonArrayImpl;
+import jdk.incubator.internal.util.json.JsonArrayImpl;
/**
* The interface that represents JSON array.
*
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonAssertionException.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
similarity index 79%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonAssertionException.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
index 82e3f219..42767d1f 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonAssertionException.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
public class JsonAssertionException extends RuntimeException {
public JsonAssertionException(String message) {
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonBoolean.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
similarity index 96%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonBoolean.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
index d9e5395e..bff9d9ae 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonBoolean.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
@@ -23,8 +23,8 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
-import jdk.sandbox.internal.util.json.JsonBooleanImpl;
+package jdk.incubator.java.util.json;
+import jdk.incubator.internal.util.json.JsonBooleanImpl;
/**
* The interface that represents JSON boolean.
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNull.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
similarity index 95%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNull.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
index 1c760812..b6a7396f 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNull.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
@@ -23,8 +23,8 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
-import jdk.sandbox.internal.util.json.JsonNullImpl;
+package jdk.incubator.java.util.json;
+import jdk.incubator.internal.util.json.JsonNullImpl;
/**
* The interface that represents JSON null.
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNumber.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNumber.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
index 0caa7eaf..ebd46ded 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonNumber.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
@@ -23,8 +23,8 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
-import jdk.sandbox.internal.util.json.JsonNumberImpl;
+package jdk.incubator.java.util.json;
+import jdk.incubator.internal.util.json.JsonNumberImpl;
/**
* The interface that represents JSON number, an arbitrary-precision
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonObject.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
similarity index 97%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonObject.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
index 1db166de..555c6e82 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonObject.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
@@ -23,14 +23,14 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
-import jdk.sandbox.internal.util.json.JsonObjectImpl;
+import jdk.incubator.internal.util.json.JsonObjectImpl;
/**
* The interface that represents JSON object.
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonParseException.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonParseException.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
index ee0f4921..acad8fd6 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonParseException.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
@@ -23,7 +23,7 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import java.io.Serial;
/**
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonString.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
similarity index 96%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonString.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
index 38170b3d..c090966b 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonString.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
@@ -23,11 +23,11 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import java.util.Objects;
-import jdk.sandbox.internal.util.json.JsonStringImpl;
-import jdk.sandbox.internal.util.json.Utils;
+import jdk.incubator.internal.util.json.JsonStringImpl;
+import jdk.incubator.internal.util.json.Utils;
/**
* The interface that represents a JSON string.
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonValue.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
similarity index 99%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/JsonValue.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
index f62ac210..2a548c3a 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/JsonValue.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
@@ -23,8 +23,8 @@
* questions.
*/
-package jdk.sandbox.java.util.json;
-import jdk.sandbox.internal.util.json.Utils;
+package jdk.incubator.java.util.json;
+import jdk.incubator.internal.util.json.Utils;
import java.util.List;
import java.util.Map;
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/package-info.java b/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
similarity index 98%
rename from json-java21/src/main/java/jdk/sandbox/java/util/json/package-info.java
rename to json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
index 61f971b4..8b54d5bc 100644
--- a/json-java21/src/main/java/jdk/sandbox/java/util/json/package-info.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
@@ -65,4 +65,4 @@
* Object Notation (JSON) Data Interchange Format
* @since 99
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
similarity index 88%
rename from json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java
rename to json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
index c1c7d6be..4aba2b58 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonParserTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
@@ -1,10 +1,10 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonBoolean;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonPatternMatchingTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
similarity index 82%
rename from json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonPatternMatchingTests.java
rename to json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
index 73d5fe38..d03e3f5a 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonPatternMatchingTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
@@ -1,12 +1,12 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonBoolean;
-import jdk.sandbox.java.util.json.JsonNull;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
similarity index 95%
rename from json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java
rename to json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
index 21acbd45..93692eb1 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/JsonRecordMappingTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
@@ -1,11 +1,11 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
-import jdk.sandbox.java.util.json.JsonArray;
-import jdk.sandbox.java.util.json.JsonNumber;
-import jdk.sandbox.java.util.json.JsonObject;
-import jdk.sandbox.java.util.json.JsonString;
-import jdk.sandbox.java.util.json.JsonValue;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
diff --git a/json-java21/src/test/java/jdk/sandbox/internal/util/json/LazyConstantTest.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/LazyConstantTest.java
similarity index 99%
rename from json-java21/src/test/java/jdk/sandbox/internal/util/json/LazyConstantTest.java
rename to json-java21/src/test/java/jdk/incubator/internal/util/json/LazyConstantTest.java
index 8c1d28eb..1a877333 100644
--- a/json-java21/src/test/java/jdk/sandbox/internal/util/json/LazyConstantTest.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/LazyConstantTest.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.internal.util.json;
+package jdk.incubator.internal.util.json;
import org.junit.jupiter.api.Test;
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/EscapedKeyBugTest.java b/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
similarity index 98%
rename from json-java21/src/test/java/jdk/sandbox/java/util/json/EscapedKeyBugTest.java
rename to json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
index b18d2619..ac340b92 100644
--- a/json-java21/src/test/java/jdk/sandbox/java/util/json/EscapedKeyBugTest.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
@@ -30,7 +30,7 @@
* @run junit EscapedKeyBugTest
*/
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/ReadmeDemoTests.java b/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
similarity index 99%
rename from json-java21/src/test/java/jdk/sandbox/java/util/json/ReadmeDemoTests.java
rename to json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
index 1899aad0..e8ef76ce 100644
--- a/json-java21/src/test/java/jdk/sandbox/java/util/json/ReadmeDemoTests.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import org.junit.jupiter.api.Test;
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/TestJsonNumberOfDouble.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
similarity index 87%
rename from json-java21/src/test/java/jdk/sandbox/java/util/json/TestJsonNumberOfDouble.java
rename to json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
index 7413c286..f224ab48 100644
--- a/json-java21/src/test/java/jdk/sandbox/java/util/json/TestJsonNumberOfDouble.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
@@ -1,4 +1,4 @@
-package jdk.sandbox.java.util.json;
+package jdk.incubator.java.util.json;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
@@ -29,6 +29,6 @@ void ofDoubleThenToLongForIntegralDouble() {
void ofDoubleThenToLongForNonIntegralShouldThrow() {
var jn = JsonNumber.of(123.45);
assertThatThrownBy(() -> jn.toLong())
- .isInstanceOf(jdk.sandbox.java.util.json.JsonAssertionException.class);
+ .isInstanceOf(jdk.incubator.java.util.json.JsonAssertionException.class);
}
}
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/examples/ReadmeExamples.java b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
similarity index 97%
rename from json-java21/src/test/java/jdk/sandbox/java/util/json/examples/ReadmeExamples.java
rename to json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
index 5d0c4c2e..96147547 100644
--- a/json-java21/src/test/java/jdk/sandbox/java/util/json/examples/ReadmeExamples.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
@@ -1,6 +1,6 @@
-package jdk.sandbox.java.util.json.examples;
+package jdk.incubator.java.util.json.examples;
-import jdk.sandbox.java.util.json.*;
+import jdk.incubator.java.util.json.*;
import java.util.List;
import java.util.Map;
@@ -10,7 +10,7 @@
* This file contains runnable examples that match the README documentation.
*
* To run:
- * mvn compile exec:java -Dexec.mainClass="jdk.sandbox.java.util.json.examples.ReadmeExamples"
+ * mvn compile exec:java -Dexec.mainClass="jdk.incubator.java.util.json.examples.ReadmeExamples"
*/
public class ReadmeExamples {
diff --git a/logging.properties b/logging.properties
index de1ea3ff..81106142 100644
--- a/logging.properties
+++ b/logging.properties
@@ -1,5 +1,5 @@
.level=FINE
-jdk.sandbox.compatibility.JsonTestSuiteSummary.level=FINE
+jdk.incubator.compatibility.JsonTestSuiteSummary.level=FINE
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=FINE
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
\ No newline at end of file
diff --git a/updates/2025-09-04/RefreshFromUpstream.java b/updates/2025-09-04/RefreshFromUpstream.java
index 65862653..917d1ec0 100644
--- a/updates/2025-09-04/RefreshFromUpstream.java
+++ b/updates/2025-09-04/RefreshFromUpstream.java
@@ -29,7 +29,7 @@ void main() throws Exception {
// Local repo paths
Path repoRoot = Paths.get("").toAbsolutePath().normalize();
- Path localImplDir = repoRoot.resolve("json-java21/src/main/java/jdk/sandbox/internal/util/json");
+ Path localImplDir = repoRoot.resolve("json-java21/src/main/java/jdk/incubator/internal/util/json");
if (!Files.isDirectory(localImplDir)) {
System.err.println("Local impl dir not found: " + localImplDir);
System.exit(1);
diff --git a/updates/2025-09-04/transform_upstream.py b/updates/2025-09-04/transform_upstream.py
index c0d048eb..08b29293 100644
--- a/updates/2025-09-04/transform_upstream.py
+++ b/updates/2025-09-04/transform_upstream.py
@@ -1,7 +1,7 @@
import os, sys, re, shutil
SRC = 'updates/2025-09-04/upstream/jdk.internal.util.json'
-DST = 'json-java21/src/main/java/jdk/sandbox/internal/util/json'
+DST = 'json-java21/src/main/java/jdk/incubator/internal/util/json'
def read(path):
f = open(path, 'r')
@@ -28,9 +28,9 @@ def write_safe(path, text):
def transform(text, name):
# package
- text = re.sub(r'^package\s+jdk\.internal\.util\.json;', 'package jdk.sandbox.internal.util.json;', text, flags=re.M)
+ text = re.sub(r'^package\s+jdk\.internal\.util\.json;', 'package jdk.incubator.internal.util.json;', text, flags=re.M)
# imports for public API
- text = re.sub(r'^(\s*import\s+)java\.util\.json\.', r'\1jdk.sandbox.java.util.json.', text, flags=re.M)
+ text = re.sub(r'^(\s*import\s+)java\.util\.json\.', r'\1jdk.incubator.java.util.json.', text, flags=re.M)
# annotations (single-line)
text = re.sub(r'^\s*@(?:jdk\.internal\..*|ValueBased|StableValue).*\n', '', text, flags=re.M)
# remove import of ValueBased if present
From b9fe3ef2c570ec6eb8f9e26fdcd4eb14339aed45 Mon Sep 17 00:00:00 2001
From: Simon Massey <322608+simbo1905@users.noreply.github.com>
Date: Sun, 30 Aug 2026 07:36:28 +0100
Subject: [PATCH 5/9] Issue #145 uplift upstream jdk.incubator.json sources at
43325738c
Take upstream public API (11 files incl. new JsonValueException, package-info)
and impl (10 files incl. new JsonGenerator, JsonValueSupport) with mechanical
transforms: package mapping jdk.incubator.json.impl -> jdk.incubator.internal.util.json,
jdk.incubator.json -> jdk.incubator.java.util.json; unnamed _ variables named
(ignored/v); Utils.powExact polyfill re-applied (upstream uses Math.powExact);
LazyConstant polyfill preserved (API identical). Deleted JsonAssertionException
(replaced by upstream JsonValueException), JsonValueImpl (folded into
JsonValueSupport) and dead StableValue polyfill. Docs updated accordingly.
---
json-java21/AGENTS.md | 11 +-
.../internal/util/json/JsonArrayImpl.java | 39 +-
.../internal/util/json/JsonBooleanImpl.java | 21 +-
.../internal/util/json/JsonGenerator.java | 164 ++++++++
.../internal/util/json/JsonNullImpl.java | 14 +-
.../internal/util/json/JsonNumberImpl.java | 124 +++---
.../internal/util/json/JsonObjectImpl.java | 46 +--
.../internal/util/json/JsonParser.java | 359 ++++++++++++------
.../internal/util/json/JsonStringImpl.java | 32 +-
.../internal/util/json/JsonValueImpl.java | 20 -
.../internal/util/json/JsonValueSupport.java | 46 +++
.../internal/util/json/LazyConstant.java | 2 +-
.../internal/util/json/StableValue.java | 83 ----
.../incubator/internal/util/json/Utils.java | 119 +++---
.../jdk/incubator/java/util/json/Json.java | 142 +++----
.../incubator/java/util/json/JsonArray.java | 47 +--
.../util/json/JsonAssertionException.java | 8 -
.../incubator/java/util/json/JsonBoolean.java | 30 +-
.../incubator/java/util/json/JsonNull.java | 15 +-
.../incubator/java/util/json/JsonNumber.java | 123 +++---
.../incubator/java/util/json/JsonObject.java | 76 ++--
.../java/util/json/JsonParseException.java | 33 +-
.../incubator/java/util/json/JsonString.java | 66 ++--
.../incubator/java/util/json/JsonValue.java | 327 +++++++---------
.../java/util/json/JsonValueException.java | 69 ++++
.../java/util/json/package-info.java | 173 +++++++--
.../internal/util/json/JsonParserTests.java | 28 +-
.../util/json/JsonPatternMatchingTests.java | 22 +-
.../util/json/JsonRecordMappingTests.java | 20 +-
29 files changed, 1230 insertions(+), 1029 deletions(-)
create mode 100644 json-java21/src/main/java/jdk/incubator/internal/util/json/JsonGenerator.java
delete mode 100644 json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
create mode 100644 json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueSupport.java
delete mode 100644 json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
delete mode 100644 json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
create mode 100644 json-java21/src/main/java/jdk/incubator/java/util/json/JsonValueException.java
diff --git a/json-java21/AGENTS.md b/json-java21/AGENTS.md
index 3cb67150..0691ff39 100644
--- a/json-java21/AGENTS.md
+++ b/json-java21/AGENTS.md
@@ -106,14 +106,14 @@ upstream call sites, **no import or call-site rewrite is needed**; upstream `Laz
usages compile unchanged against the polyfill.
This file is NOT from upstream and must be preserved during sync. The legacy
-`StableValue.java` polyfill (for the pre-`c1a4f80` upstream API) is unused dead code and is
+`StableValue.java` polyfill (for the pre-`c1a4f80` upstream API) was unused dead code and was
removed during the incubator uplift.
#### 4.4 DO NOT Convert JavaDoc to JEP 467 Markdown
If upstream uses `/** ... */` style, DO NOT convert them to our `/// ...` format; we will not edit the upstream files more than the absolute minimum to get them to run on Java 21.
-#### 4.5 JsonAssertionException (Shipped Upstream)
-Upstream at `c1a4f80` DOES ship `java/util/json/JsonAssertionException.java`; it is NOT a local
+#### 4.5 JsonValueException (Shipped Upstream)
+Upstream at `c1a4f80` DOES ship `java/util/json/JsonValueException.java`; it is NOT a local
addition. Our copy is a minimized mechanical backport of the upstream file (copyright header,
javadoc and `@Serial serialVersionUID` stripped; behaviour identical). Take the upstream file
with the standard transforms of 4.1/4.2; do not treat it as local-only.
@@ -169,8 +169,9 @@ $(command -v mvnd || command -v mvn || command -v ./mvnw) clean test -pl json-ja
| `jdk/incubator/internal/util/json/LazyConstant.java` | Java 21 polyfill for the JDK `java.lang.LazyConstant` API used by upstream since `c1a4f80` |
| `jdk/incubator/demo/JsonDemo.java` | Demonstration/example code |
-Note: `JsonAssertionException.java` is shipped upstream (see step 4.5) and
-`StableValue.java` is unused legacy pending removal; neither is a local addition anymore.
+Note: since the `43325738c` uplift, upstream ships `JsonValueException.java` instead of
+`JsonValueException.java`, and `StableValue.java` has been removed; neither is a
+local addition.
## Transformation Example
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
index e5b1f224..f61c0e22 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonArrayImpl.java
@@ -27,13 +27,15 @@
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import jdk.incubator.java.util.json.JsonArray;
import jdk.incubator.java.util.json.JsonValue;
+
/**
* JsonArray implementation class
*/
-public final class JsonArrayImpl implements JsonArray, JsonValueImpl {
+public final class JsonArrayImpl implements JsonArray, JsonValueSupport {
private final List theValues;
private final int offset;
@@ -49,11 +51,24 @@ public JsonArrayImpl(List from, int o, char[] d) {
doc = d;
}
+ // Conversion override
@Override
- public List elements() {
+ public List asList() {
return Collections.unmodifiableList(theValues);
}
+ // Navigation overrides (on default) -> bypass the unmodifiable wrap
+ @Override
+ public JsonValue get(int index) {
+ try {
+ return theValues.get(index);
+ } catch (IndexOutOfBoundsException ignored) {
+ throw Utils.composeError(this, String.format(Locale.ROOT,
+ "JsonArray index %d out of bounds for length %d.",
+ index, theValues.size()));
+ }
+ }
+
@Override
public char[] doc() {
return doc;
@@ -66,24 +81,6 @@ public int offset() {
@Override
public String toString() {
- var s = new StringBuilder("[");
- for (JsonValue v: elements()) {
- s.append(v.toString()).append(",");
- }
- if (!elements().isEmpty()) {
- s.setLength(s.length() - 1); // trim final comma
- }
- return s.append("]").toString();
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof JsonArray oja &&
- elements().equals(oja.elements());
- }
-
- @Override
- public int hashCode() {
- return elements().hashCode();
+ return JsonGenerator.toCompactString(this);
}
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
index f9f9a6c3..48891050 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonBooleanImpl.java
@@ -26,26 +26,27 @@
package jdk.incubator.internal.util.json;
import jdk.incubator.java.util.json.JsonBoolean;
+
/**
* JsonBoolean implementation class
*/
-public final class JsonBooleanImpl implements JsonBoolean, JsonValueImpl {
+public final class JsonBooleanImpl implements JsonBoolean, JsonValueSupport {
- private final Boolean theBoolean;
+ private final boolean theBoolean;
private final int offset;
private final char[] doc;
public static final JsonBooleanImpl TRUE = new JsonBooleanImpl(true, null, -1);
public static final JsonBooleanImpl FALSE = new JsonBooleanImpl(false, null, -1);
- public JsonBooleanImpl(Boolean bool, char[] doc, int offset) {
+ public JsonBooleanImpl(boolean bool, char[] doc, int offset) {
theBoolean = bool;
this.doc = doc;
this.offset = offset;
}
@Override
- public boolean bool() {
+ public boolean asBoolean() {
return theBoolean;
}
@@ -61,16 +62,6 @@ public int offset() {
@Override
public String toString() {
- return String.valueOf(bool());
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof JsonBoolean ojb && bool() == ojb.bool();
- }
-
- @Override
- public int hashCode() {
- return Boolean.hashCode(bool());
+ return String.valueOf(asBoolean());
}
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonGenerator.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonGenerator.java
new file mode 100644
index 00000000..3d7ab878
--- /dev/null
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonGenerator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.internal.util.json;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.Map;
+
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonValue;
+
+/**
+ * Generates JSON text for JsonValue, either for toString() or toDisplayString().
+ */
+public final class JsonGenerator {
+
+ private sealed interface StructureFrame permits ArrayFrame, ObjectFrame {}
+
+ private static final class ArrayFrame implements StructureFrame {
+ private final Iterator elements;
+ private final int depth; // For indentation
+ private boolean first; // Whether iterator points to first value
+
+ private ArrayFrame(Iterator elements, int depth) {
+ this.elements = elements;
+ this.depth = depth;
+ first = true;
+ }
+ }
+
+ private static final class ObjectFrame implements StructureFrame {
+ private final Iterator> members;
+ private final int depth; // For indentation
+ private boolean first; // Whether iterator points to first entry
+
+ private ObjectFrame(Iterator> members, int depth) {
+ this.members = members;
+ this.depth = depth;
+ first = true;
+ }
+ }
+
+ // Generates JSON text for Json[Object|Array].toString()
+ public static String toCompactString(JsonValue jv) {
+ return generate(jv, "", false);
+ }
+
+ // Generates JSON text for Json.toDisplayString()
+ public static String toDisplayString(JsonValue jv, String indent) {
+ return generate(jv, indent, true);
+ }
+
+ private static String generate(JsonValue root, String indent, boolean isDisplay) {
+ var sb = new StringBuilder();
+ Deque stack = new ArrayDeque<>();
+ enterValue(root, sb, stack, 0, isDisplay);
+
+ while (!stack.isEmpty()) {
+ switch (stack.peek()) {
+ case ArrayFrame af -> {
+ var elements = af.elements;
+ if (elements.hasNext()) {
+ if (af.first) {
+ af.first = false;
+ } else {
+ sb.append(isDisplay ? ",\n" : ",");
+ }
+ if (isDisplay) {
+ sb.repeat(indent, af.depth + 1);
+ }
+ enterValue(elements.next(), sb, stack, af.depth + 1, isDisplay);
+ } else {
+ if (isDisplay) {
+ sb.append("\n");
+ sb.repeat(indent, af.depth);
+ }
+ sb.append("]");
+ stack.pop();
+ }
+ }
+ case ObjectFrame of -> {
+ var members = of.members;
+ if (members.hasNext()) {
+ if (of.first) {
+ of.first = false;
+ } else {
+ sb.append(isDisplay ? ",\n" : ",");
+ }
+ var entry = members.next();
+ if (isDisplay) {
+ sb.repeat(indent, of.depth + 1);
+ }
+ sb.append('"')
+ .append(Utils.escape(entry.getKey()))
+ .append("\":")
+ .append(isDisplay ? " " : "");
+ enterValue(entry.getValue(), sb, stack, of.depth + 1, isDisplay);
+ } else {
+ if (isDisplay) {
+ sb.append("\n");
+ sb.repeat(indent, of.depth);
+ }
+ sb.append("}");
+ stack.pop();
+ }
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void enterValue(JsonValue jv, StringBuilder sb, Deque stack,
+ int depth, boolean isDisplay) {
+ switch (jv) {
+ case JsonArray ja -> {
+ var elements = ja.asList().iterator();
+ if (!elements.hasNext()) {
+ sb.append("[]");
+ } else {
+ sb.append(isDisplay ? "[\n" : "[");
+ stack.push(new ArrayFrame(elements, depth));
+ }
+ }
+ case JsonObject jo -> {
+ var members = jo.asMap().entrySet().iterator();
+ if (!members.hasNext()) {
+ sb.append("{}");
+ } else {
+ sb.append(isDisplay ? "{\n" : "{");
+ stack.push(new ObjectFrame(members, depth));
+ }
+ }
+ default -> sb.append(jv);
+ }
+ }
+
+ // Instantiation is not allowed
+ private JsonGenerator() {}
+}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
index b55ced05..2260e9a1 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNullImpl.java
@@ -26,17 +26,17 @@
package jdk.incubator.internal.util.json;
import jdk.incubator.java.util.json.JsonNull;
+
/**
* JsonNull implementation class
*/
-public final class JsonNullImpl implements JsonNull, JsonValueImpl {
+public final class JsonNullImpl implements JsonNull, JsonValueSupport {
private final int offset;
private final char[] doc;
public static final JsonNullImpl NULL = new JsonNullImpl(null, -1);
private static final String VALUE = "null";
- private static final int HASH = VALUE.hashCode();
public JsonNullImpl(char[] doc, int offset) {
this.doc = doc;
@@ -57,14 +57,4 @@ public int offset() {
public String toString() {
return VALUE;
}
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof JsonNull;
- }
-
- @Override
- public int hashCode() {
- return HASH;
- }
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
index 6a823829..fafc0409 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonNumberImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,28 +25,29 @@
package jdk.incubator.internal.util.json;
-import java.util.Locale;
-
import java.util.Optional;
import jdk.incubator.java.util.json.JsonNumber;
+
/**
* JsonNumber implementation class
*/
-public final class JsonNumberImpl implements JsonNumber, JsonValueImpl {
+public final class JsonNumberImpl implements JsonNumber, JsonValueSupport {
private final char[] doc;
private final int startOffset;
private final int endOffset;
private final int decimalOffset;
private final int exponentOffset;
+ private final boolean fromFactory;
private final LazyConstant numString = LazyConstant.of(this::initNumString);
private final LazyConstant> numInteger = LazyConstant.of(this::initNumInteger);
private final LazyConstant> numLong = LazyConstant.of(this::initNumLong);
private final LazyConstant> numDouble = LazyConstant.of(this::initNumDouble);
- public JsonNumberImpl(char[] doc, int start, int end, int dec, int exp) {
+ public JsonNumberImpl(char[] doc, boolean factory, int start, int end, int dec, int exp) {
this.doc = doc;
+ fromFactory = factory;
startOffset = start;
endOffset = end;
decimalOffset = dec;
@@ -54,31 +55,31 @@ public JsonNumberImpl(char[] doc, int start, int end, int dec, int exp) {
}
@Override
- public int toInt() {
+ public int asInt() {
return numInteger.get().orElseThrow(() ->
Utils.composeError(this, this + " cannot be represented as an int."));
}
@Override
- public long toLong() {
+ public long asLong() {
return numLong.get().orElseThrow(() ->
Utils.composeError(this, this + " cannot be represented as a long."));
}
@Override
- public double toDouble() {
+ public double asDouble() {
return numDouble.get().orElseThrow(() ->
Utils.composeError(this, this + " cannot be represented as a double."));
}
@Override
public char[] doc() {
- return doc;
+ return fromFactory ? null : doc;
}
@Override
public int offset() {
- return startOffset;
+ return fromFactory ? -1 : startOffset;
}
@Override
@@ -86,17 +87,6 @@ public String toString() {
return numString.get();
}
- @Override
- public boolean equals(Object o) {
- return o instanceof JsonNumber ojn &&
- toString().compareToIgnoreCase(ojn.toString()) == 0;
- }
-
- @Override
- public int hashCode() {
- return toString().toLowerCase(Locale.ROOT).hashCode();
- }
-
// LazyConstants initializers
private String initNumString() {
return new String(doc, startOffset, endOffset - startOffset);
@@ -105,62 +95,65 @@ private String initNumString() {
private Optional initNumInteger() {
try {
return numLong.get().map(Math::toIntExact);
- } catch(ArithmeticException e) {
+ } catch (ArithmeticException ignored) {
return Optional.empty();
}
}
- // 4 cases: Fully integral, has decimal, has exponent, has decimal and exponent
private Optional initNumLong() {
try {
if (decimalOffset == -1 && exponentOffset == -1) {
- // Parseable Long format
+ // Fast-path immediate parseable Long format
return Optional.of(Long.parseLong(numString.get()));
} else {
- // Decimal or exponent exists, can't parse w/ Long::parseLong
- if (exponentOffset != -1) {
- // Exponent exists
- // Calculate exponent value
- int exp = Math.abs(Integer.parseInt(new String(doc,
- exponentOffset + 1, endOffset - exponentOffset - 1), 10));
- long sig;
- long scale;
- if (decimalOffset == -1) {
- // Exponent with no decimal
- sig = Long.parseLong(new String(doc, startOffset, exponentOffset - startOffset));
+ // Decimal or exponent exists, derive value from
+ // following format -> sig * 10^power
+ // E.g. 54.32e1
+ // sE is 'e' index / fL is 2 / exp is 1 / pow is -1 / sig is 5432 / scale is 0.1
+ int sigEnd = exponentOffset == -1 ? endOffset : exponentOffset;
+ int fracLen = decimalOffset == -1 ? 0 : sigEnd - decimalOffset - 1;
+ int strippedZeros = 0;
+
+ // Remove trailing zeros from the significand and compensate in the power.
+ // We do this to avoid possible overflow when we parse the coefficient as a long.
+ // E.g. 9223372036854775807.000000 or 922337203685477580700.0e-2
+ while (sigEnd > startOffset) {
+ var c = doc[sigEnd - 1];
+ if (c == '0') {
+ sigEnd--;
+ strippedZeros++;
+ } else if (c == '.') {
+ sigEnd--;
} else {
- // Exponent with decimal
- for (int i = decimalOffset + exp + 1; i < exponentOffset; i++) {
- if (doc[i] != '0') {
- return Optional.empty();
- }
- }
- var shiftedFractionPart = new String(doc, decimalOffset + 1, Math.min(exp, exponentOffset - decimalOffset - 1));
- exp = exp - shiftedFractionPart.length();
- sig = Long.parseLong(new String(doc, startOffset, decimalOffset - startOffset) + shiftedFractionPart);
- }
- scale = Utils.powExact(10L, exp);
- if (doc[exponentOffset + 1] != '-') {
- return Optional.of(Math.multiplyExact(sig, scale));
- } else {
- if (sig % scale == 0) {
- return Optional.of(Math.divideExact(sig, scale));
- } else {
- return Optional.empty();
- }
+ break;
}
+ }
+
+ // A zero significand represents zero regardless of exponent size.
+ // For non-zero significands, an exponent outside int range cannot be
+ // offset by fraction length or trailing zeros within a Java char[] input.
+ // This must be checked before calculating exp.
+ if (sigEnd == startOffset || (doc[startOffset] == '-' && sigEnd == startOffset + 1)) {
+ return Optional.of(0L);
+ }
+ int exp = exponentOffset == -1 ? 0 : Integer.parseInt(new String(doc,
+ exponentOffset + 1, endOffset - exponentOffset - 1));
+ int power = Math.addExact(Math.subtractExact(exp, fracLen), strippedZeros);
+ long sig = decimalOffset == -1 || sigEnd <= decimalOffset
+ ? Long.parseLong(new String(doc, startOffset, sigEnd - startOffset))
+ : Long.parseLong(new String(doc, startOffset, decimalOffset - startOffset) +
+ new String(doc, decimalOffset + 1, sigEnd - decimalOffset - 1));
+ if (power >= 0) {
+ long scale = Utils.powExact(10L, power);
+ return Optional.of(Math.multiplyExact(sig, scale));
} else {
- // Decimal with no exponent
- for (int i = decimalOffset + 1; i < endOffset; i++) {
- if (doc[i] != '0') {
- return Optional.empty();
- }
- }
- return Optional.of(Long.parseLong(new String(doc,
- startOffset, decimalOffset - startOffset), 10));
+ long scale = Utils.powExact(10L, Math.negateExact(power));
+ return sig % scale == 0
+ ? Optional.of(Math.divideExact(sig, scale))
+ : Optional.empty(); // fractional leftover, so not representable as long
}
}
- } catch(NumberFormatException | ArithmeticException e) {}
+ } catch (NumberFormatException | ArithmeticException ignored) {}
return Optional.empty();
}
@@ -171,4 +164,9 @@ private Optional initNumDouble() {
}
return Optional.empty();
}
+
+ // Helper which converts this JNI to one that sees itself as created from a factory
+ public JsonNumber toFactoryValue() {
+ return new JsonNumberImpl(doc, true, startOffset, endOffset, decimalOffset, exponentOffset);
+ }
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
index 3906cadf..d2ba8277 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonObjectImpl.java
@@ -27,13 +27,16 @@
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import jdk.incubator.java.util.json.JsonObject;
import jdk.incubator.java.util.json.JsonValue;
+
/**
* JsonObject implementation class
*/
-public final class JsonObjectImpl implements JsonObject, JsonValueImpl {
+public final class JsonObjectImpl implements JsonObject, JsonValueSupport {
private final Map theMembers;
private final int offset;
@@ -49,44 +52,41 @@ public JsonObjectImpl(Map map, int o, char[] d) {
doc = d;
}
+ // Conversion override
@Override
- public Map members() {
+ public Map asMap() {
return Collections.unmodifiableMap(theMembers);
}
+ // Navigation overrides (on default) -> bypass the unmodifiable wrap
@Override
- public char[] doc() {
- return doc;
+ public JsonValue get(String name) {
+ Objects.requireNonNull(name);
+ return switch (theMembers.get(name)) {
+ case JsonValue jv -> jv;
+ case null -> throw Utils.composeError(this,
+ "JsonObject member \"%s\" does not exist.".formatted(name));
+ };
}
@Override
- public int offset() {
- return offset;
+ public Optional tryGet(String name) {
+ Objects.requireNonNull(name);
+ return Optional.ofNullable(theMembers.get(name));
}
@Override
- public String toString() {
- var s = new StringBuilder("{");
- for (Map.Entry kv: members().entrySet()) {
- // Escape the key (which is stored as unescaped) to conform to JSON syntax
- s.append("\"").append(Utils.escape(kv.getKey())).append("\":")
- .append(kv.getValue().toString())
- .append(",");
- }
- if (!members().isEmpty()) {
- s.setLength(s.length() - 1); // trim final comma
- }
- return s.append("}").toString();
+ public char[] doc() {
+ return doc;
}
@Override
- public boolean equals(Object o) {
- return o instanceof JsonObject ojo &&
- members().equals(ojo.members());
+ public int offset() {
+ return offset;
}
@Override
- public int hashCode() {
- return members().hashCode();
+ public String toString() {
+ return JsonGenerator.toCompactString(this);
}
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
index 0342510f..39213268 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,20 +25,20 @@
package jdk.incubator.internal.util.json;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
-import java.util.function.Supplier;
-import jdk.incubator.java.util.json.JsonArray;
-import jdk.incubator.java.util.json.JsonObject;
import jdk.incubator.java.util.json.JsonParseException;
import jdk.incubator.java.util.json.JsonString;
import jdk.incubator.java.util.json.JsonValue;
/**
- * Parses a JSON Document char[] into a tree of JsonValues. JsonObject and JsonArray
+ * Parses a JSON text char[] into a tree of JsonValues. JsonObject and JsonArray
* nodes create their data structures which maintain the connection to children.
* JsonNumber and JsonString contain only a start and end offset, which
* are used to lazily procure their underlying value/string on demand.
@@ -55,15 +55,61 @@ public final class JsonParser {
private int line;
private int lineStart;
+ // The "root" value
+ private JsonValue root;
+ // LIFO stack for objects/arrays partially parsed. Popped upon parsing completion
+ private final Deque containers = new ArrayDeque<>();
+
+ // Object/Array containers holding parsed child values
+ private sealed interface Container permits ObjectContainer, ArrayContainer {}
+ private static final class ObjectContainer implements Container {
+ final int startOffset;
+ final Map members = new LinkedHashMap<>();
+ String name;
+ ObjectState state = ObjectState.NAME_OR_END;
+
+ ObjectContainer(int startOffset) {
+ this.startOffset = startOffset;
+ }
+ }
+ private static final class ArrayContainer implements Container {
+ final int startOffset;
+ final List elements = new ArrayList<>();
+ ArrayState state = ArrayState.VALUE_OR_END;
+
+ ArrayContainer(int startOffset) {
+ this.startOffset = startOffset;
+ }
+ }
+
+ // Object/Array container states for the next possible input
+ private enum ObjectState {
+ NAME_OR_END, VALUE, COMMA_OR_END
+ }
+ private enum ArrayState {
+ VALUE_OR_END, COMMA_OR_END
+ }
+
public JsonParser(char[] doc) {
this.doc = doc;
}
// Parses the lone JsonValue root
public JsonValue parseRoot() {
- JsonValue root = parseValue();
+ containers.clear();
+ root = null;
+
+ parseValue();
+
+ while (!containers.isEmpty()) {
+ switch (containers.peek()) {
+ case ObjectContainer oc -> parseObject(oc);
+ case ArrayContainer ac -> parseArray(ac);
+ }
+ }
+
if (hasInput()) {
- throw failure("Additional value(s) were found after the JSON Value");
+ throw valueFailure(0, "Additional value(s) were found after the JSON Value");
}
return root;
}
@@ -74,26 +120,35 @@ public JsonValue parseRoot() {
* JSON-text = ws value ws
* See https://datatracker.ietf.org/doc/html/rfc8259#section-3
*/
- private JsonValue parseValue() {
+ private void parseValue() {
skipWhitespaces();
+ int start = offset;
+
if (!hasInput()) {
- throw failure("Expected a JSON Object, Array, String, Number, Boolean, or Null");
+ throw valueFailure(start, "Expected a JSON Object, Array, String, Number, Boolean, or Null");
}
- var val = switch (doc[offset]) {
- case '{' -> parseObject();
- case '[' -> parseArray();
- case '"' -> parseString();
- case 't' -> parseTrue();
- case 'f' -> parseFalse();
- case 'n' -> parseNull();
+
+ switch (doc[offset]) {
+ case '{' -> {
+ offset++;
+ skipWhitespaces();
+ containers.push(new ObjectContainer(start));
+ }
+ case '[' -> {
+ offset++;
+ skipWhitespaces();
+ containers.push(new ArrayContainer(start));
+ }
+ case '"' -> finishValue(parseString(), start);
+ case 't' -> finishValue(parseTrue(), start);
+ case 'f' -> finishValue(parseFalse(), start);
+ case 'n' -> finishValue(parseNull(), start);
// While JSON Number does not support leading '+', '.', or 'e'
// we still accept, so that we can provide a better error message
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', 'e', '.'
- -> parseNumber();
- default -> throw failure(UNEXPECTED_VAL);
- };
- skipWhitespaces();
- return val;
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '-', '+', 'e', '.' -> finishValue(parseNumber(), start);
+ default -> throw valueFailure(start, UNEXPECTED_VAL);
+ }
}
/*
@@ -101,43 +156,115 @@ private JsonValue parseValue() {
* No offsets are required as member values hold their own offsets.
* See https://datatracker.ietf.org/doc/html/rfc8259#section-4
*/
- private JsonObject parseObject() {
- var startO = offset++; // Walk past the '{'
- skipWhitespaces();
- // Check for empty case
- if (charEquals('}')) {
- return new JsonObjectImpl(Map.of(), startO, doc);
+ private void parseObject(ObjectContainer oc) {
+ switch (oc.state) {
+ case NAME_OR_END -> {
+ if (!hasInput()) {
+ throw structureFailure(oc.startOffset, "JSON Object is not closed with a brace");
+ }
+ if (oc.members.isEmpty() && charEquals('}')) {
+ finishObject(oc);
+ return;
+ }
+
+ int nameStart = offset;
+ var name = parseName(oc.startOffset);
+ int nameLine = line;
+ int nameLineStart = lineStart;
+
+ skipWhitespaces();
+ if (!charEquals(':')) {
+ throw structureFailure(oc.startOffset, "Expected a colon after the member name");
+ }
+ if (oc.members.containsKey(name)) {
+ throw failure(nameStart, nameLine, nameLineStart,
+ "Duplicate member name: \"%s\" was already parsed".formatted(Utils.escape(name)), oc.startOffset, true);
+ }
+ oc.name = name;
+ oc.state = ObjectState.VALUE;
+ }
+ case VALUE -> parseValue();
+ case COMMA_OR_END -> {
+ if (charEquals('}')) {
+ finishObject(oc);
+ } else if (charEquals(',')) {
+ skipWhitespaces();
+ oc.state = ObjectState.NAME_OR_END;
+ } else {
+ throw structureFailure(oc.startOffset, "JSON Object is not closed with a brace");
+ }
+ }
}
- var members = new LinkedHashMap();
- while (hasInput()) {
- // Get the member name, which should be unescaped
- // Why not parse the name as a JsonString and then return its value()?
- // Would requires 2 passes; we should build the String as we parse.
- var name = parseName();
- var nameOffset = offset;
-
- // Move from name to ':'
- skipWhitespaces();
- if (!charEquals(':')) {
- throw failure(
- "Expected a colon after the member name");
+ }
+
+ private void finishObject(ObjectContainer oc) {
+ containers.pop();
+ finishValue(new JsonObjectImpl(oc.members, oc.startOffset, doc), oc.startOffset);
+ }
+
+ /*
+ * The parsed JsonArray contains a List which holds all lazy children
+ * elements. No offsets are required as children values hold their own offsets.
+ * See https://datatracker.ietf.org/doc/html/rfc8259#section-5
+ */
+ private void parseArray(ArrayContainer ac) {
+ switch (ac.state) {
+ case VALUE_OR_END -> {
+ if (!hasInput()) {
+ throw structureFailure(ac.startOffset,
+ "JSON Array is not closed with a bracket");
+ }
+ if (ac.elements.isEmpty() && charEquals(']')) {
+ finishArray(ac);
+ } else {
+ parseValue();
+ }
+ }
+ case COMMA_OR_END -> {
+ if (charEquals(']')) {
+ finishArray(ac);
+ } else if (charEquals(',')) {
+ ac.state = ArrayState.VALUE_OR_END;
+ } else {
+ throw structureFailure(ac.startOffset,
+ "JSON Array is not closed with a bracket");
+ }
}
+ }
+ }
- if (members.putIfAbsent(name, parseValue()) != null) {
- throw failure(nameOffset, "The duplicate member name: \"%s\" was already parsed".formatted(name));
+ private void finishArray(ArrayContainer ac) {
+ containers.pop();
+ finishValue(new JsonArrayImpl(ac.elements, ac.startOffset, doc), ac.startOffset);
+ }
+
+ // Place the value as either the root, an object member, or an array element.
+ private void finishValue(JsonValue jv, int start) {
+ if (hasInput()) {
+ switch (doc[offset]) {
+ // Attribute incorrect values appended directly on a valid value as
+ // error on the value rather than its enclosing structure.
+ case ']', '}', ',', ' ', '\t', '\r', '\n' -> {}
+ default -> throw valueFailure(start, "Unexpected content after JSON value");
}
+ }
+ skipWhitespaces();
- // Ensure current char is either ',' or '}'
- if (charEquals('}')) {
- return new JsonObjectImpl(members, startO, doc);
- } else if (charEquals(',')) {
- skipWhitespaces();
- } else {
- // Neither ',' nor '}' so fail
- break;
+ if (containers.isEmpty()) {
+ root = jv;
+ } else {
+ switch (containers.peek()) {
+ case ObjectContainer oc -> {
+ oc.members.put(oc.name, jv);
+ oc.name = null;
+ oc.state = ObjectState.COMMA_OR_END;
+ }
+ case ArrayContainer ac -> {
+ ac.elements.add(jv);
+ ac.state = ArrayState.COMMA_OR_END;
+ }
}
}
- throw failure("JSON Object is not closed with a brace");
}
/*
@@ -145,13 +272,14 @@ private JsonObject parseObject() {
* unescaped value.
* See https://datatracker.ietf.org/doc/html/rfc8259#section-8.3
*/
- private String parseName() {
+ private String parseName(int objStart) {
if (!charEquals('"')) {
- throw failure("Expecting a JSON Object member name");
+ throw structureFailure(objStart, "Expecting a JSON Object member name");
}
var escape = false;
boolean useBldr = false;
var start = offset;
+
for (; hasInput(); offset++) {
var c = doc[offset];
if (escape) {
@@ -165,10 +293,11 @@ private String parseName() {
case 'r' -> c = '\r';
case 't' -> c = '\t';
case 'u' -> {
- c = codeUnit();
+ c = codeUnit(objStart, true);
escapeLength = 4;
}
- default -> throw failure(UNRECOGNIZED_ESCAPE_SEQUENCE.formatted(c));
+ default -> throw structureFailure(objStart,
+ UNRECOGNIZED_ESCAPE_SEQUENCE.formatted(formatChar(c)));
}
if (!useBldr) {
// Append everything up to the first escape sequence
@@ -179,7 +308,7 @@ private String parseName() {
} else if (c == '\\') {
escape = true;
continue;
- } else if (c == '\"') {
+ } else if (c == '"') {
offset++;
if (useBldr) {
var name = sb.get().toString();
@@ -189,39 +318,13 @@ private String parseName() {
return new String(doc, start, offset - start - 1);
}
} else if (c < ' ') {
- throw failure(UNESCAPED_CONTROL_CODE);
+ throw structureFailure(objStart, UNESCAPED_CONTROL_CODE);
}
if (useBldr) {
sb.get().append(c);
}
}
- throw failure(UNCLOSED_STRING.formatted("JSON Object member name"));
- }
-
- /*
- * The parsed JsonArray contains a List which holds all lazy children
- * elements. No offsets are required as children values hold their own offsets.
- * See https://datatracker.ietf.org/doc/html/rfc8259#section-5
- */
- private JsonArray parseArray() {
- var startO = offset++; // Walk past the '['
- skipWhitespaces();
- // Check for empty case
- if (charEquals(']')) {
- return new JsonArrayImpl(List.of(), startO, doc);
- }
- var list = new ArrayList();
- while (hasInput()) {
- // Get the JsonValue
- list.add(parseValue());
- // Ensure current char is either ']' or ','
- if (charEquals(']')) {
- return new JsonArrayImpl(list, startO, doc);
- } else if (!charEquals(',')) {
- break;
- }
- }
- throw failure("JSON Array is not closed with a bracket");
+ throw structureFailure(objStart, UNCLOSED_STRING.formatted("JSON Object member name"));
}
/*
@@ -242,20 +345,21 @@ private JsonString parseString() {
switch (c) {
// Allowed JSON escapes
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't' -> {}
- case 'u' -> codeUnit();
- default -> throw failure(UNRECOGNIZED_ESCAPE_SEQUENCE.formatted(c));
+ case 'u' -> codeUnit(start, false);
+ default -> throw valueFailure(start,
+ UNRECOGNIZED_ESCAPE_SEQUENCE.formatted((formatChar(c))));
}
escape = false;
} else if (c == '\\') {
hasEscape = true;
escape = true;
- } else if (c == '\"') {
- return new JsonStringImpl(doc, start, ++offset, hasEscape);
+ } else if (c == '"') {
+ return new JsonStringImpl(doc, false, start, ++offset, hasEscape);
} else if (c < ' ') {
- throw failure(UNESCAPED_CONTROL_CODE);
+ throw valueFailure(start, UNESCAPED_CONTROL_CODE);
}
}
- throw failure(UNCLOSED_STRING.formatted("JSON String"));
+ throw valueFailure(start, UNCLOSED_STRING.formatted("JSON String"));
}
private JsonBooleanImpl parseTrue() {
@@ -263,7 +367,7 @@ private JsonBooleanImpl parseTrue() {
if (charEquals('r') && charEquals('u') && charEquals('e')) {
return new JsonBooleanImpl(true, doc, start);
}
- throw failure(UNEXPECTED_VAL);
+ throw valueFailure(start, UNEXPECTED_VAL);
}
private JsonBooleanImpl parseFalse() {
@@ -272,7 +376,7 @@ private JsonBooleanImpl parseFalse() {
&& charEquals('e')) {
return new JsonBooleanImpl(false, doc, start);
}
- throw failure(UNEXPECTED_VAL);
+ throw valueFailure(start, UNEXPECTED_VAL);
}
private JsonNullImpl parseNull() {
@@ -280,7 +384,7 @@ private JsonNullImpl parseNull() {
if (charEquals('u') && charEquals('l') && charEquals('l')) {
return new JsonNullImpl(doc, start);
}
- throw failure(UNEXPECTED_VAL);
+ throw valueFailure(start, UNEXPECTED_VAL);
}
/*
@@ -302,20 +406,20 @@ private JsonNumberImpl parseNumber() {
var c = doc[offset];
switch (c) {
case '-' -> {
- if (offset != start && expOff == -1 || sawSign) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ if ((offset != start && expOff == -1) || havePart || sawSign) {
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
}
sawSign = true;
}
case '+' -> {
if (expOff == -1 || havePart || sawSign) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
}
sawSign = true;
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> {
if (decOff == -1 && expOff == -1 && sawZero) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted('0'));
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted('0'));
}
if (doc[offset] == '0' && !havePart) {
sawZero = true;
@@ -323,11 +427,11 @@ private JsonNumberImpl parseNumber() {
havePart = true;
}
case '.' -> {
- if (decOff != -1) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ if (decOff != -1 || expOff != -1) {
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
} else {
if (!havePart) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
}
decOff = offset;
havePart = false;
@@ -335,10 +439,10 @@ private JsonNumberImpl parseNumber() {
}
case 'e', 'E' -> {
if (expOff != -1) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
} else {
if (!havePart) {
- throw failure(INVALID_POSITION_IN_NUMBER.formatted(c));
+ throw valueFailure(start, INVALID_POSITION_IN_NUMBER.formatted(c));
}
expOff = offset;
havePart = false;
@@ -352,9 +456,9 @@ private JsonNumberImpl parseNumber() {
}
}
if (!havePart) {
- throw failure("Input expected after '[.|e|E]'");
+ throw valueFailure(start, "Input expected after '[.|e|E]'");
}
- return new JsonNumberImpl(doc, start, offset, decOff, expOff);
+ return new JsonNumberImpl(doc, false, start, offset, decOff, expOff);
}
// Utility functions
@@ -364,11 +468,12 @@ private StringBuilder initSb() {
}
// Unescapes the Unicode escape sequence and produces a char
- private char codeUnit() {
+ private char codeUnit(int start, boolean structural) {
char val = 0;
int end = offset + 4;
if (end >= doc.length) {
- throw failure("Invalid Unicode escape sequence. Expected four hex digits");
+ throw failure("Invalid Unicode escape sequence. Expected four hex digits",
+ start, structural);
}
while (offset < end) {
char c = doc[++offset];
@@ -379,13 +484,15 @@ private char codeUnit() {
case 'a', 'b', 'c', 'd', 'e', 'f' -> c - 'a' + 10;
case 'A', 'B', 'C', 'D', 'E', 'F' -> c - 'A' + 10;
default -> throw failure(
- "Invalid Unicode escape sequence. '%c' is not a hex digit".formatted(c));
+ "Invalid Unicode escape sequence. '%s' is not a hex digit"
+ .formatted((formatChar(c))),
+ start, structural);
});
}
return val;
}
- // Returns true if the parser has not yet reached the end of the Document
+ // Returns true if the parser has not yet reached the end of the text
private boolean hasInput() {
return offset < doc.length;
}
@@ -403,7 +510,7 @@ private void skipWhitespaces() {
// see https://datatracker.ietf.org/doc/html/rfc8259#section-2
private boolean notWhitespace() {
return switch (doc[offset]) {
- case ' ', '\t','\r' -> false;
+ case ' ', '\t', '\r' -> false;
case '\n' -> {
// Increments the line and lineStart
line++;
@@ -424,22 +531,40 @@ private boolean charEquals(char c) {
return false;
}
- private JsonParseException failure(String message) {
- return failure(offset, message);
+ // To be thrown when a structure is incorrect, which derives the path from the enclosing structure itself
+ private JsonParseException structureFailure(int start, String message) {
+ return failure(offset, line, lineStart, message, start, true);
+ }
+
+ // To be thrown when a "value" is incorrect, which derives the path from the value
+ private JsonParseException valueFailure(int start, String message) {
+ return failure(offset, line, lineStart, message, start, false);
+ }
+
+ private JsonParseException failure(String message, int recentStart, boolean structural) {
+ return failure(offset, line, lineStart, message, recentStart, structural);
}
- private JsonParseException failure(int off, String message) {
+ private JsonParseException failure(int off, int l, int ls, String message, int head, boolean structural) {
// Non-revealing message does not produce input source String
- var pos = off - lineStart;
- return new JsonParseException("%s. Location: line %d, position %d."
- .formatted(message, line, pos), line, pos);
+ var pos = off - ls;
+ var path = Utils.getParsingPath(head, doc, structural);
+ return new JsonParseException(String.format(Locale.ROOT,
+ "%s.%s Location: line %d, position %d.",
+ message, path, l, pos), l, pos);
+ }
+
+ private static String formatChar(char c) {
+ return c >= 0x21 && c <= 0x7E ? // Space is represented as "\u0020" for visibility
+ Character.toString(c) :
+ String.format(Locale.ROOT, "\\u%04X", (int)c);
}
// Parsing error messages ----------------------
private static final String UNEXPECTED_VAL =
"Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null";
private static final String UNRECOGNIZED_ESCAPE_SEQUENCE =
- "Unrecognized escape sequence: \"\\%c\"";
+ "Unrecognized escape sequence: \"\\%s\"";
private static final String UNESCAPED_CONTROL_CODE =
"Unescaped control code";
private static final String UNCLOSED_STRING =
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
index c75559bb..0657714d 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonStringImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,23 +26,25 @@
package jdk.incubator.internal.util.json;
import jdk.incubator.java.util.json.JsonString;
+
/**
* JsonString implementation class
*/
-public final class JsonStringImpl implements JsonString, JsonValueImpl {
+public final class JsonStringImpl implements JsonString, JsonValueSupport {
private final char[] doc;
private final int startOffset;
private final int endOffset;
private final boolean hasEscape;
+ private final boolean fromFactory;
// The String instance representing this JSON string for `toString()`.
- // It always conforms to JSON syntax. If created by parsing a JSON document,
+ // It always conforms to JSON syntax. If created by parsing a JSON text,
// it matches the original text exactly. If created via the factory method,
// non-conformant characters are properly escaped.
private final LazyConstant jsonStr = LazyConstant.of(this::initJsonStr);
- // The String instance returned by `string()`. Escaped characters are unescaped.
+ // The String instance returned by `asString()`. Escaped characters are unescaped.
private final LazyConstant value = LazyConstant.of(this::unescape);
// LazyConstants initializers
@@ -50,26 +52,27 @@ private String initJsonStr() {
return new String(doc, startOffset, endOffset - startOffset);
}
- public JsonStringImpl(char[] doc, int start, int end, boolean escape) {
+ public JsonStringImpl(char[] doc, boolean factory, int start, int end, boolean escape) {
this.doc = doc;
+ fromFactory = factory;
startOffset = start;
endOffset = end;
hasEscape = escape;
}
@Override
- public String string() {
+ public String asString() {
return value.get();
}
@Override
public char[] doc() {
- return doc;
+ return fromFactory ? null : doc;
}
@Override
public int offset() {
- return startOffset;
+ return fromFactory ? -1 : startOffset;
}
@Override
@@ -103,7 +106,7 @@ private String unescape() {
case 'r' -> c = '\r';
case 't' -> c = '\t';
case 'u' -> {
- // Will not throw NFE, document parse already validated input
+ // Will not throw NFE, text parse already validated input
c = (char) Integer.parseInt(new String(doc, offset + 1, 4), 16);
offset += 4;
}
@@ -117,15 +120,4 @@ private String unescape() {
}
return sb.toString();
}
-
- @Override
- public boolean equals(Object o) {
- return o instanceof JsonString ojs &&
- string().equals(ojs.string());
- }
-
- @Override
- public int hashCode() {
- return string().hashCode();
- }
}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
deleted file mode 100644
index ab62a364..00000000
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueImpl.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package jdk.incubator.internal.util.json;
-
-/**
- * Used for JsonAssertionException error message building.
- */
-public sealed interface JsonValueImpl
- permits JsonArrayImpl, JsonBooleanImpl, JsonNullImpl, JsonNumberImpl, JsonObjectImpl, JsonStringImpl {
-
- /**
- * Return access to the underlying document, if it was parsed.
- * Otherwise, return null.
- */
- char[] doc();
-
- /**
- * Return the associated offset of the JsonValue in the document, if it was parsed.
- * Otherwise, return -1.
- */
- int offset();
-}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueSupport.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueSupport.java
new file mode 100644
index 00000000..b838668c
--- /dev/null
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/JsonValueSupport.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.internal.util.json;
+
+/**
+ * Provides support methods for {@code JsonValue} implementation classes,
+ * primarily for constructing {@code JsonValueException} error messages.
+ */
+public sealed interface JsonValueSupport
+ permits JsonArrayImpl, JsonBooleanImpl, JsonNullImpl, JsonNumberImpl, JsonObjectImpl, JsonStringImpl {
+
+ /**
+ * Return access to the underlying JSON text, if it was parsed.
+ * Otherwise, return null.
+ */
+ char[] doc();
+
+ /**
+ * Return the associated offset of the JsonValue in the JSON text,
+ * if it was parsed. Otherwise, return -1.
+ */
+ int offset();
+}
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
index ece6888d..a31b9e0f 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
@@ -5,7 +5,7 @@
/// Polyfill for JDK's LazyConstant using double-checked locking pattern
/// for thread-safe lazy initialization.
///
-/// This provides a simpler API than StableValue:
+/// This provides a simpler API than the legacy StableValue:
/// - `LazyConstant.of(Supplier)` - creates a lazy constant
/// - `.get()` - gets the value (computing if needed)
class LazyConstant {
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
deleted file mode 100644
index 1d3bdbf9..00000000
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/StableValue.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package jdk.incubator.internal.util.json;
-
-import java.util.function.Supplier;
-
-/// Mimics JDK's StableValue using double-checked locking pattern
-/// for thread-safe lazy initialization.
-class StableValue {
- private volatile T value;
- private final Object lock = new Object();
-
- private StableValue() {
- }
-
- public static StableValue of() {
- return new StableValue<>();
- }
-
- public T orElse(T defaultValue) {
- T result = value;
- return result != null ? result : defaultValue;
- }
-
- public T orElseSet(Supplier supplier) {
- T result = value;
- if (result == null) {
- synchronized (lock) {
- result = value;
- if (result == null) {
- value = result = supplier.get();
- }
- }
- }
- return result;
- }
-
- public void setOrThrow(T newValue) {
- if (value != null) {
- throw new IllegalStateException("Value already set");
- }
- synchronized (lock) {
- if (value != null) {
- throw new IllegalStateException("Value already set");
- }
- value = newValue;
- }
- }
-
- public static Supplier supplier(Supplier supplier) {
- return new Supplier<>() {
- private volatile T cached;
- private final Object supplierLock = new Object();
-
- @Override
- public T get() {
- T result = cached;
- if (result == null) {
- synchronized (supplierLock) {
- result = cached;
- if (result == null) {
- cached = result = supplier.get();
- }
- }
- }
- return result;
- }
-
- @Override
- public String toString() {
- return get().toString();
- }
-
- @Override
- public int hashCode() {
- return get().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return get().equals(obj);
- }
- };
- }
-}
\ No newline at end of file
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
index be50413d..eb3adacb 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,7 @@
package jdk.incubator.internal.util.json;
import jdk.incubator.java.util.json.JsonArray;
-import jdk.incubator.java.util.json.JsonAssertionException;
+import jdk.incubator.java.util.json.JsonValueException;
import jdk.incubator.java.util.json.JsonBoolean;
import jdk.incubator.java.util.json.JsonNull;
import jdk.incubator.java.util.json.JsonNumber;
@@ -34,6 +34,10 @@
import jdk.incubator.java.util.json.JsonString;
import jdk.incubator.java.util.json.JsonValue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
/**
* Shared utilities for Json classes.
*/
@@ -45,8 +49,6 @@ private Utils() {}
/*
* Escapes a String to ensure it is a valid JSON String.
* Backslash, double quote, and control chars are escaped.
- * Providing this method in Utils allows for a bypass of `JsonString.of(str).value()`
- * for the toString representation of JsonObject member names.
*/
public static String escape(String str) {
StringBuilder sb = null; // Lazy init
@@ -62,38 +64,37 @@ public static String escape(String str) {
if (sb == null) {
sb = new StringBuilder().append(str, 0, i);
}
- // 2 Char escapes (Non-control characters)
- if (c == '\\') {
- sb.append('\\').append(c);
- } else if (c == '"') {
- sb.append('\\').append(c);
- // 2 Char escapes (Control characters)
+ sb.append('\\');
+ // Non-control characters
+ if (c == '\\' || c == '"') {
+ sb.append(c);
+ // 2 Char escapes (Control characters)
} else if (c == '\b') {
- sb.append('\\').append('b');
+ sb.append('b');
} else if (c == '\f') {
- sb.append('\\').append('f');
+ sb.append('f');
} else if (c == '\n') {
- sb.append('\\').append('n');
+ sb.append('n');
} else if (c == '\r') {
- sb.append('\\').append('r');
+ sb.append('r');
} else if (c == '\t') {
- sb.append('\\').append('t');
- // All other chars requiring Unicode escape sequence
+ sb.append('t');
} else {
- sb.append('\\').append('u').append(String.format("%04X", (int) c));
+ // All other chars requiring Unicode escape sequence
+ sb.append('u').append(String.format("%04X", (int) c));
}
}
}
return sb == null ? str : sb.toString();
}
- public static JsonAssertionException composeError(JsonValue jv, String message) {
- return new JsonAssertionException(message +
- (jv instanceof JsonValueImpl jvi && jvi.doc() != null ? JsonPath.getPath(jvi) : ""));
+ public static JsonValueException composeError(JsonValue jv, String message) {
+ return new JsonValueException(message +
+ (jv instanceof JsonValueSupport jvs && jvs.doc() != null ? JsonPath.getValuePath(jvs) : ""));
}
// Use to compose an exception when casting to an incorrect type
- public static JsonAssertionException composeTypeError(JsonValue jv, String expected) {
+ public static JsonValueException composeTypeError(JsonValue jv, String expected) {
var actual = switch (jv) {
case JsonObject v -> "JsonObject";
case JsonArray v -> "JsonArray";
@@ -105,34 +106,63 @@ public static JsonAssertionException composeTypeError(JsonValue jv, String expec
return composeError(jv, "%s is not a %s.".formatted(actual, expected));
}
- // This class is responsible for creating the path produced by JAE.
+ static String getParsingPath(int offset, char[] doc, boolean structural) {
+ return JsonPath.getParsingPath(offset, doc, structural);
+ }
+
+ // This class is responsible for creating the path produced by JsonValueException
+ // and JsonParseException. As a result, the appropriate method should be used
+ // as the path semantics differ between the two exception types.
// Backtracks from the offset of the offending JSON element to the root.
private static final class JsonPath {
-
private final int offset;
private final char[] doc;
// Tracked and incremented during path creation
private int line;
private int pos;
- private JsonPath(JsonValueImpl jvi) {
- this.offset = jvi.offset();
- this.doc = jvi.doc();
+ private JsonPath(int offset, char[] doc) {
+ this.offset = offset;
+ this.doc = doc;
+ }
+
+ // JsonParseException path produces a contextual path which may not always lead to a primitive
+ // value, but can occur in the structure itself. The offsets in the exception
+ // message should ultimately be derived from the parser state.
+ private static String getParsingPath(int offset, char[] doc, boolean structural) {
+ var pathParts = new ArrayList();
+ // If we encounter an error within the structural state, but not within a value itself
+ // we need to manually insert the brace otherwise backtracking skips it
+ if (structural) {
+ // Structural parsing cases
+ if (doc[offset] == '[') {
+ pathParts.add("[");
+ } else if (doc[offset] == '{') {
+ pathParts.add("{");
+ }
+ }
+ return " Path: \"%s\".".formatted(new JsonPath(offset, doc).parseToRoot(pathParts));
}
- private static String getPath(JsonValueImpl jvi) {
- return new JsonPath(jvi).parseToRoot();
+ // JsonValueException path produces a path that always leads to a value, and should provide
+ // the correct line and pos positions derived from the JV itself
+ private static String getValuePath(JsonValueSupport jvs) {
+ var pathParts = new ArrayList();
+ var jp = new JsonPath(jvs.offset(), jvs.doc());
+ var path = jp.parseToRoot(pathParts);
+ // After path is produced, line and pos should be value bearing
+ return String.format(Locale.ROOT,
+ " Path: \"%s\". Location: line %d, position %d.",
+ path, jp.line, jp.pos);
}
- private String parseToRoot() {
- var sb = new StringBuilder();
- // Updates the sb
- toPath(offset, sb);
+ private String parseToRoot(List pathParts) {
+ toPath(offset, pathParts);
// If no new line encountered, pos is the starting offset value
if (line == 0) {
pos = offset;
}
- return " Path: \"%s\". Location: line %d, position %d.".formatted(sb.toString(), line, pos);
+ return String.join("", pathParts.reversed());
}
private void addLine(int curr) {
@@ -142,28 +172,29 @@ private void addLine(int curr) {
}
}
- // Void return type, builds the passed StringBuilder
- private void toPath(int offset, StringBuilder sb) {
+ // List is populated upon completion. It contains the path
+ // to the root in reverse order.
+ private void toPath(int offset, List pathParts) {
// Walk past starting char and white space
offset = walkWhitespace(offset - 1);
// If offset is -1, we found the root and are finished
- if (offset != -1) {
+ while (offset > -1) {
// Node case
offset = switch (doc[offset]) {
// Does the actual appending
// Walks to the node's starting [ or {
- case ',', '[' -> arrayNode(offset, sb);
- case ':' -> objectNode(offset, sb);
+ case ',', '[' -> arrayNode(offset, pathParts);
+ case ':' -> objectNode(offset, pathParts);
default -> throw new InternalError();
};
- toPath(offset, sb);
+ offset = walkWhitespace(offset - 1);
}
}
private int walkWhitespace(int offset) {
while (offset >= 0) {
var ws = switch (doc[offset]) {
- case ' ', '\t','\r' -> true;
+ case ' ', '\t', '\r' -> true;
case '\n' -> {
addLine(offset);
yield true;
@@ -180,7 +211,7 @@ private int walkWhitespace(int offset) {
// Backtracking from an element in a JsonArray either expects a ',' or '['
// E.g. " [ val ... " or " [ foo, val "
- private int arrayNode(int offset, StringBuilder sb) {
+ private int arrayNode(int offset, List pathParts) {
int aDepth = 0;
int oDepth = 0;
int values = 0;
@@ -213,13 +244,13 @@ private int arrayNode(int offset, StringBuilder sb) {
}
offset--;
}
- sb.insert(0, '[' + String.valueOf(values));
+ pathParts.add('[' + String.valueOf(values));
return offset;
}
// Unlike arrayNode, always expects a ':'
// Regardless of value position, always preceded by a member name and colon
- private int objectNode(int offset, StringBuilder sb) {
+ private int objectNode(int offset, List pathParts) {
offset--; // Walk past ':'
int depth = 0;
int nameStart = 0;
@@ -243,7 +274,7 @@ private int objectNode(int offset, StringBuilder sb) {
}
// Add the name
- sb.insert(0, '{' + new String(doc, nameStart, nameEnd - nameStart));
+ pathParts.add('{' + new String(doc, nameStart, nameEnd - nameStart));
boolean inString = false;
// Move to parent offset
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java b/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
index 77518a35..f005d4e3 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/Json.java
@@ -24,49 +24,48 @@
*/
package jdk.incubator.java.util.json;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.stream.Collectors;
+
import jdk.incubator.internal.util.json.JsonParser;
-import jdk.incubator.internal.util.json.Utils;
+import jdk.incubator.internal.util.json.JsonGenerator;
/**
- * This class provides static methods for parsing and generating JSON documents
+ * This class provides static methods for parsing and generating JSON texts.
*
*
* {@link #parse(String)} and {@link #parse(char[])} produce a {@code JsonValue}
- * by parsing data adhering to the JSON syntax defined in RFC 8259. Unsuccessful
- * parsing throws a {@link JsonParseException}.
+ * by parsing data adhering to the JSON syntax defined in RFC 8259.
+ * {@snippet lang = java:
+ * JsonValue root = Json.parse(jsonText);
+ * }
+ * Successful parsing guarantees there are no syntax errors. Unsuccessful
+ * parsing throws a {@link JsonParseException}. Note that duplicate names in
+ * a {@code JsonObject} also result in this exception.
*
- * {@link #toDisplayString(JsonValue, int)} produces a
+ * {@link #toDisplayString(JsonValue, String)} produces a
* JSON text representation of the given {@code JsonValue} suitable for display.
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259 RFC 8259: The JavaScript
* Object Notation (JSON) Data Interchange Format
- * @since 99
+ * @since 28
*/
public final class Json {
/**
- * Parses and creates a {@code JsonValue} from the given JSON document.
- * If parsing succeeds, it guarantees that the input document conforms to
- * the JSON syntax. If the document contains any JSON object that has
+ * Parses and creates a {@code JsonValue} from the given JSON text.
+ * If parsing succeeds, it guarantees that the input text conforms to
+ * the JSON syntax. If the text contains any JSON object that has
* duplicate names, a {@code JsonParseException} is thrown.
*
- * {@code JsonObject}s preserve the order of their members declared in and parsed from
- * the JSON document.
+ * {@code JsonObject}s preserve the order of members in the input JSON
+ * text.
*
* @implNote {@code JsonValue}s created by this method may produce their
* underlying value representation lazily.
*
- * @param in the input JSON document as {@code String}. Non-null.
- * @throws JsonParseException if the input JSON document does not conform
- * to the JSON document format or a JSON object containing
+ * @param in the input JSON text as {@code String}. Non-null.
+ * @throws JsonParseException if the input JSON text does not conform
+ * to the JSON text format or a JSON object containing
* duplicate names is encountered.
* @throws NullPointerException if {@code in} is {@code null}
* @return the parsed {@code JsonValue}
@@ -77,20 +76,21 @@ public static JsonValue parse(String in) {
}
/**
- * Parses and creates a {@code JsonValue} from the given JSON document.
- * If parsing succeeds, it guarantees that the input document conforms to
- * the JSON syntax. If the document contains any JSON object that has
- * duplicate names, a {@code JsonParseException} is thrown.
+ * Parses and creates a {@code JsonValue} from the given JSON text.
+ * If parsing succeeds, it guarantees that the input text conforms to
+ * the JSON syntax. If the text contains any JSON object that has
+ * duplicate names, a {@code JsonParseException} is thrown. After parsing,
+ * changes to the input array have no effect on the returned {@code JsonValue}.
*
* {@code JsonObject}s preserve the order of their members declared in and parsed from
- * the JSON document.
+ * the JSON text.
*
* @implNote {@code JsonValue}s created by this method may produce their
* underlying value representation lazily.
*
- * @param in the input JSON document as {@code char[]}. Non-null.
- * @throws JsonParseException if the input JSON document does not conform
- * to the JSON document format or a JSON object containing
+ * @param in the input JSON text as {@code char[]}. Non-null.
+ * @throws JsonParseException if the input JSON text does not conform
+ * to the JSON text format or a JSON object containing
* duplicate names is encountered.
* @throws NullPointerException if {@code in} is {@code null}
* @return the parsed {@code JsonValue}
@@ -98,84 +98,34 @@ public static JsonValue parse(String in) {
public static JsonValue parse(char[] in) {
Objects.requireNonNull(in);
// Defensive copy on input. Ensure source is immutable.
- return new JsonParser(Arrays.copyOf(in, in.length)).parseRoot();
+ return new JsonParser(in.clone()).parseRoot();
}
/**
* {@return the String representation of the given {@code JsonValue} that conforms
* to the JSON syntax} As opposed to the compact output returned by {@link
- * JsonValue#toString()}, this method returns a JSON string that is better
- * suited for display.
+ * JsonValue#toString()}, this method returns JSON text that is better
+ * suited for display. The {@code indent} parameter specifies the indentation
+ * string used for each line and may contain only JSON insignificant whitespace
+ * characters: space ({@code ' '}), horizontal tab ({@code '\t'}), line feed
+ * ({@code '\n'}), or carriage return ({@code '\r'}).
*
* @param value the {@code JsonValue} to create the display string from. Non-null.
- * @param indent the number of spaces used for the indentation. Zero or positive.
- * @throws NullPointerException if {@code value} is {@code null}
- * @throws IllegalArgumentException if {@code indent} is a negative number
+ * @param indent the {@code String} for the indentation. Non-null.
+ * @throws IllegalArgumentException if {@code indent} contains characters other
+ * than insignificant whitespace characters.
+ * @throws NullPointerException if {@code value} or {@code indent} is {@code null}
* @see JsonValue#toString()
*/
- public static String toDisplayString(JsonValue value, int indent) {
+ public static String toDisplayString(JsonValue value, String indent) {
Objects.requireNonNull(value);
- if (indent < 0) {
- throw new IllegalArgumentException("indent is negative");
- }
- var s = new StringBuilder();
- toDisplayString(value, s, 0, indent, false);
- return s.toString();
- }
-
- private static void toDisplayString(JsonValue jv, StringBuilder s, int col, int indent, boolean isField) {
- switch (jv) {
- case JsonObject jo -> toDisplayString(jo, s, col, indent, isField);
- case JsonArray ja -> toDisplayString(ja, s, col, indent, isField);
- default -> s.append(" ".repeat(isField ? 1 : col)).append(jv);
- }
- }
-
- private static void toDisplayString(JsonObject jo, StringBuilder s,
- int col, int indent, boolean isField) {
- var prefix = " ".repeat(col);
- if (isField) {
- s.append(" ");
- } else {
- s.append(prefix);
- }
- if (jo.members().isEmpty()) {
- s.append("{}");
- } else {
- s.append("{\n");
- jo.members().forEach((name, val) -> {
- s.append(prefix)
- .append(" ".repeat(indent))
- .append("\"")
- .append(name)
- .append("\":");
- Json.toDisplayString(val, s, col + indent, indent, true);
- s.append(",\n");
- });
- s.setLength(s.length() - 2); // trim final comma
- s.append("\n").append(prefix).append("}");
- }
- }
-
- private static void toDisplayString(JsonArray ja, StringBuilder s,
- int col, int indent, boolean isField) {
- var prefix = " ".repeat(col);
- if (isField) {
- s.append(" ");
- } else {
- s.append(prefix);
- }
- if (ja.elements().isEmpty()) {
- s.append("[]");
- } else {
- s.append("[\n");
- for (JsonValue v : ja.elements()) {
- Json.toDisplayString(v, s, col + indent, indent, false);
- s.append(",\n");
- }
- s.setLength(s.length() - 2); // trim final comma/newline
- s.append("\n").append(prefix).append("]");
+ Objects.requireNonNull(indent);
+ if (!indent.chars().allMatch(c ->
+ c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
+ throw new IllegalArgumentException("indent contains non-insignificant" +
+ " whitespace: " + indent);
}
+ return JsonGenerator.toDisplayString(value, indent);
}
// no instantiation is allowed for this class
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
index c0e7d2de..734c3f61 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonArray.java
@@ -25,12 +25,10 @@
package jdk.incubator.java.util.json;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
import jdk.incubator.internal.util.json.JsonArrayImpl;
+
/**
* The interface that represents JSON array.
*
@@ -39,7 +37,7 @@
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259#section-5 RFC 8259:
* The JavaScript Object Notation (JSON) Data Interchange Format - Arrays
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonArray extends JsonValue {
@@ -47,21 +45,22 @@ public non-sealed interface JsonArray extends JsonValue {
* {@inheritDoc}
*/
@Override
- List elements();
+ List asList();
/**
* {@inheritDoc}
*
* @param index {@inheritDoc}
- * @throws JsonAssertionException if the given index is out of bounds
+ * @throws JsonValueException if the given index is out of bounds
*/
- default JsonValue element(int index) {
+ @Override
+ default JsonValue get(int index) {
// Overridden to specify
- return JsonValue.super.element(index);
+ return JsonValue.super.get(index);
}
/**
- * {@return the {@code JsonArray} created from the given
+ * {@return the {@code JsonArray} whose contents are copied from the given
* list of {@code JsonValue}s}
*
* @param src the list of {@code JsonValue}s. Non-null.
@@ -71,34 +70,6 @@ default JsonValue element(int index) {
static JsonArray of(List extends JsonValue> src) {
// Careful not to use List::contains on src for null checking which
// throws NPE for immutable lists
- return new JsonArrayImpl(src
- .stream()
- .map(Objects::requireNonNull)
- .collect(Collectors.toCollection(ArrayList::new))
- );
+ return new JsonArrayImpl(List.copyOf(src));
}
-
- /**
- * {@return {@code true} if the given object is also a {@code JsonArray}
- * and the two {@code JsonArray}s represent the same elements} Two
- * {@code JsonArray}s {@code ja1} and {@code ja2} represent the same
- * elements if {@code ja1.elements().equals(ja2.elements())}.
- *
- * @see #elements()
- */
- @Override
- boolean equals(Object obj);
-
- /**
- * {@return the hash code value for this {@code JsonArray}} The hash code value
- * of a {@code JsonArray} is derived from the hash code of {@code JsonArray}'s
- * {@link #elements()}.
- * Thus, for two {@code JsonArray}s {@code ja1} and {@code ja2},
- * {@code ja1.equals(ja2)} implies that {@code ja1.hashCode() == ja2.hashCode()}
- * as required by the general contract of {@link Object#hashCode}.
- *
- * @see #elements()
- */
- @Override
- int hashCode();
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
deleted file mode 100644
index 42767d1f..00000000
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonAssertionException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package jdk.incubator.java.util.json;
-
-public class JsonAssertionException extends RuntimeException {
- public JsonAssertionException(String message) {
- super(message);
- }
-}
-
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
index bff9d9ae..b8117ac9 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonBoolean.java
@@ -24,10 +24,11 @@
*/
package jdk.incubator.java.util.json;
+
import jdk.incubator.internal.util.json.JsonBooleanImpl;
/**
- * The interface that represents JSON boolean.
+ * The interface that represents the JSON boolean literals, "true" and "false".
*
* A {@code JsonBoolean} can be produced by {@link Json#parse(String)}.
*
Alternatively, {@link #of(boolean)} can be used to
@@ -35,7 +36,7 @@
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259#section-3 RFC 8259:
* The JavaScript Object Notation (JSON) Data Interchange Format - Values
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonBoolean extends JsonValue {
@@ -43,7 +44,7 @@ public non-sealed interface JsonBoolean extends JsonValue {
* {@inheritDoc}
*/
@Override
- boolean bool();
+ boolean asBoolean();
/**
* {@return the {@code JsonBoolean} created from the given
@@ -54,27 +55,4 @@ public non-sealed interface JsonBoolean extends JsonValue {
static JsonBoolean of(boolean src) {
return src ? JsonBooleanImpl.TRUE : JsonBooleanImpl.FALSE;
}
-
- /**
- * {@return {@code true} if the given object is also a {@code JsonBoolean}
- * and the two {@code JsonBoolean}s represent the same boolean value} Two
- * {@code JsonBoolean}s {@code jb1} and {@code jb2} represent the same
- * boolean values if {@code jb1.bool().equals(jb2.bool())}.
- *
- * @see #bool()
- */
- @Override
- boolean equals(Object obj);
-
- /**
- * {@return the hash code value for this {@code JsonBoolean}} The hash code value
- * of a {@code JsonBoolean} is derived from the hash code of {@code JsonBoolean}'s
- * {@link #bool()}. Thus, for two {@code JsonBooleans}s {@code jb1} and {@code jb2},
- * {@code jb1.equals(jb2)} implies that {@code jb1.hashCode() == jb2.hashCode()}
- * as required by the general contract of {@link Object#hashCode}.
- *
- * @see #bool()
- */
- @Override
- int hashCode();
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
index b6a7396f..07eda0b4 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNull.java
@@ -24,6 +24,7 @@
*/
package jdk.incubator.java.util.json;
+
import jdk.incubator.internal.util.json.JsonNullImpl;
/**
@@ -32,7 +33,7 @@
* A {@code JsonNull} can be produced by {@link Json#parse(String)}.
*
Alternatively, {@link #of()} can be used to obtain a {@code JsonNull}.
*
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonNull extends JsonValue {
@@ -42,16 +43,4 @@ public non-sealed interface JsonNull extends JsonValue {
static JsonNull of() {
return JsonNullImpl.NULL;
}
-
- /**
- * {@return true if the given {@code obj} is a {@code JsonNull}}
- */
- @Override
- boolean equals(Object obj);
-
- /**
- * {@return the hash code value of this {@code JsonNull}}
- */
- @Override
- int hashCode();
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
index ebd46ded..717977f8 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonNumber.java
@@ -24,6 +24,7 @@
*/
package jdk.incubator.java.util.json;
+
import jdk.incubator.internal.util.json.JsonNumberImpl;
/**
@@ -32,74 +33,71 @@
*
* A {@code JsonNumber} can be produced by {@link Json#parse(String)}.
* When a JSON number is parsed, a {@code JsonNumber} object is created
- * as long as the parsed value adheres to the JSON number
+ * as long as the input number text adheres to the JSON number
*
* syntax.
- * Alternatively, {@link #of(double)}, {@link #of(long)}, or {@link #of(String)}
- * can be used to obtain a {@code JsonNumber}.
+ *
Alternatively, {@link #of(int)}, {@link #of(long)}, {@link #of(double)},
+ * or {@link #of(String)} can be used to obtain a {@code JsonNumber}.
* The value of the {@code JsonNumber} can be retrieved as an {@code int} with
- * {@link #toInt()}, as a {@code long} with {@link #toLong()}, or as a
- * {@code double} with {@link #toDouble()}. {@link #toString()} can be used to
- * return the string representation of the JSON number.
+ * {@link #asInt()}, as a {@code long} with {@link #asLong()}, or as a
+ * {@code double} with {@link #asDouble()}. {@link #toString()} can be used to
+ * return the string representation of the {@code JsonNumber}.
*
* @apiNote
- * To avoid precision loss when converting JSON numbers to Java types, or when
- * converting JSON numbers outside the range of {@code long} or {@code double},
+ * To avoid precision loss when converting {@code JsonNumber}s to Java types, or when
+ * converting {@code JsonNumber}s outside the range of {@code long} or {@code double},
* use {@link #toString()} to create arbitrary-precision Java objects, for
* example,
* {@snippet lang="java" :
- * new BigDecimal(JsonNumber.toString())
+ * new BigDecimal(jsonNumber.toString())
* // or if an integral number is preferred
- * new BigInteger(JsonNumber.toString())
+ * new BigInteger(jsonNumber.toString())
* // for cases with an exponent or zero fractional part
- * new BigDecimal(JsonNumber.toString()).toBigIntegerExact()
+ * new BigDecimal(jsonNumber.toString()).toBigIntegerExact()
* }
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259#section-6 RFC 8259:
* The JavaScript Object Notation (JSON) Data Interchange Format - Numbers
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonNumber extends JsonValue {
/**
* {@inheritDoc}
*
- * @throws JsonAssertionException if this {@code JsonNumber} cannot
- * be represented as an {@code int}.
+ * @throws JsonValueException if this {@code JsonNumber} is not representable
+ * as an {@code int}.
*/
@Override
- int toInt();
+ int asInt();
/**
* {@inheritDoc}
*
- * @throws JsonAssertionException if this {@code JsonNumber} cannot
- * be represented as a {@code long}.
+ * @throws JsonValueException if this {@code JsonNumber} is not representable
+ * as a {@code long}.
*/
@Override
- long toLong();
+ long asLong();
/**
* {@inheritDoc}
*
* @apiNote {@inheritDoc}
- * @implNote The JDK reference implementation uses {@link
- * Double#parseDouble(String)} to perform the conversion from string to
- * finite double.
*
- * @throws JsonAssertionException if this {@code JsonNumber} cannot
- * be represented as a finite {@code double}.
+ * @throws JsonValueException if this {@code JsonNumber} is not representable
+ * as a finite {@code double}.
*/
@Override
- double toDouble();
+ double asDouble();
/**
- * Creates a JSON number from the given {@code double} value.
- * The string representation of the JSON number created is produced by applying
+ * Creates a {@code JsonNumber} from the given {@code double} value.
+ * The string representation of the {@code JsonNumber} created is produced by applying
* {@link Double#toString(double)} on {@code num}.
*
* @param num the given {@code double} value.
- * @return a JSON number created from the {@code double} value
+ * @return a {@code JsonNumber} created from the {@code double} value
* @throws IllegalArgumentException if the given {@code double} value
* is not a finite floating-point value ({@link Double#NaN NaN},
* {@link Double#POSITIVE_INFINITY positive infinity}, or
@@ -109,70 +107,63 @@ static JsonNumber of(double num) {
if (!Double.isFinite(num)) {
throw new IllegalArgumentException("Not a valid JSON number");
}
- // Delegate to of(String) which correctly computes offsets via Json.parse()
- // Upstream bug: hardcoded decimalOffset=0 and exponentOffset=0
- return of(Double.toString(num));
+ var str = Double.toString(num);
+ return new JsonNumberImpl(str.toCharArray(), true, 0, str.length(), str.indexOf('.'), str.indexOf('E'));
}
/**
- * Creates a JSON number from the given {@code int} value.
- * The string representation of the JSON number created is produced by applying
+ * Creates a {@code JsonNumber} from the given {@code int} value.
+ * The string representation of the {@code JsonNumber} created is produced by applying
* {@link Integer#toString(int)} on {@code num}.
*
* @param num the given {@code int} value.
- * @return a JSON number created from the {@code int} value
+ * @return a {@code JsonNumber} created from the {@code int} value
*/
static JsonNumber of(int num) {
var str = Integer.toString(num);
- return new JsonNumberImpl(str.toCharArray(), 0, str.length(), -1, -1);
+ return new JsonNumberImpl(str.toCharArray(), true, 0, str.length(), -1, -1);
}
/**
- * Creates a JSON number from the given {@code long} value.
- * The string representation of the JSON number created is produced by applying
+ * Creates a {@code JsonNumber} from the given {@code long} value.
+ * The string representation of the {@code JsonNumber} created is produced by applying
* {@link Long#toString(long)} on {@code num}.
*
* @param num the given {@code long} value.
- * @return a JSON number created from the {@code long} value
+ * @return a {@code JsonNumber} created from the {@code long} value
*/
static JsonNumber of(long num) {
var str = Long.toString(num);
- return new JsonNumberImpl(str.toCharArray(), 0, str.length(), -1, -1);
+ return new JsonNumberImpl(str.toCharArray(), true, 0, str.length(), -1, -1);
}
/**
- * Creates a JSON number from the given {@code String} value.
- * The string representation of the JSON number created is equivalent to
- * {@code num}.
- *
- * @implNote The value returned is equivalent to calling:
- * {@snippet lang = "java":
- * if (Json.parse(num) instanceof JsonNumber jn) {
- * return jn;
- * }
- * }
+ * Creates a {@code JsonNumber} from the given {@code String} value.
+ * The string representation of the {@code JsonNumber} created is equivalent to
+ * {@code num} with any leading or trailing JSON insignificant whitespaces removed.
*
* @param num the given {@code String} value.
* @throws IllegalArgumentException if {@code num} is not a valid string
- * representation of a JSON number.
- * @return a JSON number created from the {@code String} value
+ * representation of a {@code JsonNumber}.
+ * @throws NullPointerException if {@code num} is {@code null}
+ * @return a {@code JsonNumber} created from the {@code String} value
*/
static JsonNumber of(String num) {
try {
- if (Json.parse(num) instanceof JsonNumber jn) {
- return jn;
+ if (Json.parse(num) instanceof JsonNumberImpl jn) {
+ return jn.toFactoryValue();
}
- } catch(JsonParseException e) {}
+ } catch (JsonParseException ignored) {}
throw new IllegalArgumentException("Not a JSON number");
}
/**
* {@return the string representation of this {@code JsonNumber}}
*
- * If this {@code JsonNumber} is created by parsing a JSON number in a JSON document,
- * it preserves the string representation in the document, regardless of its
+ * If this {@code JsonNumber} is created by parsing a JSON number in a JSON text,
+ * it preserves the string representation in the JSON text, regardless of its
* precision or range. For example, a JSON number like
- * {@code 3.141592653589793238462643383279} in the JSON document will be
+ * "3.141592653589793238462643383279" in the JSON text will be
* returned exactly as it appears.
* If this {@code JsonNumber} is created via one of the factory methods,
* such as {@link JsonNumber#of(double)}, then the string representation is
@@ -180,24 +171,4 @@ static JsonNumber of(String num) {
*/
@Override
String toString();
-
- /**
- * {@return true if the given {@code obj} is equal to this {@code JsonNumber}}
- * The comparison is based on the string representation of this {@code JsonNumber},
- * ignoring the case.
- *
- * @see #toString()
- */
- @Override
- boolean equals(Object obj);
-
- /**
- * {@return the hash code value of this {@code JsonNumber}} The returned hash code
- * is derived from the string representation of this {@code JsonNumber},
- * ignoring the case.
- *
- * @see #toString()
- */
- @Override
- int hashCode();
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
index 555c6e82..670c4d02 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonObject.java
@@ -25,40 +25,47 @@
package jdk.incubator.java.util.json;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
+
import jdk.incubator.internal.util.json.JsonObjectImpl;
/**
* The interface that represents JSON object.
*
- * A {@code JsonObject} can be produced by a {@link Json#parse(String)}.
- *
Alternatively, {@link #of(Map)} can be used to obtain a {@code JsonObject}.
+ * A {@code JsonObject} can be produced by {@link Json#parse(String)}.
+ *
+ * Alternatively, {@link #of(Map)} can be used to obtain a {@code JsonObject}.
+ *
* Implementations of {@code JsonObject} cannot be created from sources that
- * contain duplicate member names. If duplicate names appear during
- * a {@link Json#parse(String)}, a {@code JsonParseException} is thrown.
+ * contain duplicate member names. If duplicate names appear while parsing with
+ * {@link Json#parse(String)}, a {@code JsonParseException} is thrown. If duplicate
+ * member names are detected while creating a {@code JsonObject} with {@link #of(Map)},
+ * an {@code IllegalArgumentException} is thrown.
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259#section-4 RFC 8259:
* The JavaScript Object Notation (JSON) Data Interchange Format - Objects
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonObject extends JsonValue {
/**
* {@inheritDoc}
+ *
+ * @implNote {@inheritDoc}
*/
@Override
- Map members();
+ Map asMap();
/**
* {@inheritDoc}
*
* @param name {@inheritDoc}
- * @throws JsonAssertionException if there is no association with the member name
* @throws NullPointerException {@inheritDoc}
+ * @throws JsonValueException if there is no association with the member name
*/
@Override
default JsonValue get(String name) {
@@ -73,50 +80,37 @@ default JsonValue get(String name) {
* @throws NullPointerException {@inheritDoc}
*/
@Override
- default Optional getOrAbsent(String name) {
+ default Optional tryGet(String name) {
// Overridden to specify
- return JsonValue.super.getOrAbsent(name);
+ return JsonValue.super.tryGet(name);
}
/**
* {@return the {@code JsonObject} created from the given
* map of {@code String} to {@code JsonValue}s}
*
- * The {@code JsonObject}'s members occur in the same order as the given
- * map's entries.
- *
* @param map the map of {@code JsonValue}s. Non-null.
+ * @throws IllegalArgumentException if duplicate member names are given in
+ * {@code map}, including when they are encountered while iterating over
+ * the mappings of an {@link java.util.IdentityHashMap}.
* @throws NullPointerException if {@code map} is {@code null}, contains
* any keys that are {@code null}, or contains any values that are {@code null}.
*/
static JsonObject of(Map map) {
- return new JsonObjectImpl(map.entrySet() // Implicit NPE on map
- .stream()
- .collect(Collectors.toMap(
- e -> Objects.requireNonNull(e.getKey()), Map.Entry::getValue, // Implicit NPE on val
- (k, v) -> v, LinkedHashMap::new)));
- }
-
- /**
- * {@return {@code true} if the given object is also a {@code JsonObject}
- * and the two {@code JsonObject}s represent the same mappings} Two
- * {@code JsonObject}s {@code jo1} and {@code jo2} represent the same
- * mappings if {@code jo1.members().equals(jo2.members())}.
- *
- * @see #members()
- */
- @Override
- boolean equals(Object obj);
+ Objects.requireNonNull(map);
- /**
- * {@return the hash code value for this {@code JsonObject}} The hash code value
- * of a {@code JsonObject} is derived from the hash code of {@code JsonObject}'s
- * {@link #members()}. Thus, for two {@code JsonObject}s {@code jo1} and {@code jo2},
- * {@code jo1.equals(jo2)} implies that {@code jo1.hashCode() == jo2.hashCode()}
- * as required by the general contract of {@link Object#hashCode}.
- *
- * @see #members()
- */
- @Override
- int hashCode();
+ if (map.isEmpty()) {
+ return new JsonObjectImpl(Collections.emptyMap());
+ } else {
+ var m = new LinkedHashMap();
+ for (var e : map.entrySet()) {
+ var key = Objects.requireNonNull(e.getKey());
+ var value = Objects.requireNonNull(e.getValue());
+ if (m.putIfAbsent(key, value) != null) {
+ throw new IllegalArgumentException("Duplicate member name: " + key);
+ }
+ }
+ return new JsonObjectImpl(m);
+ }
+ }
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
index acad8fd6..3e7e3766 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonParseException.java
@@ -24,29 +24,31 @@
*/
package jdk.incubator.java.util.json;
+
import java.io.Serial;
/**
* Signals that an error has been detected while parsing the
- * JSON document. This exception is thrown if the value supplied
- * to the {@link Json#parse(String) Json::parse} methods is not valid JSON
- * syntax, or contains a JSON object with duplicate names.
+ * JSON text. This exception is thrown if the value supplied
+ * to either {@link Json#parse(String)} or {@link Json#parse(char[])}
+ * is not valid JSON syntax, or contains a JSON object with duplicate
+ * names.
*
- * @since 99
+ * @since 28
*/
-public class JsonParseException extends RuntimeException {
+public final class JsonParseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 7022545379651073390L;
/**
- * Position of the error line in the document
+ * Zero-based line number of the error
* @serial
*/
private final int line;
/**
- * Position of the error position in the document
+ * Zero-based position of the error within the line
* @serial
*/
private final int pos;
@@ -54,24 +56,33 @@ public class JsonParseException extends RuntimeException {
/**
* Constructs a JsonParseException with the specified detail message.
* @param message the detail message
- * @param line the line of the error on parsing the document
- * @param pos the position of the error on parsing the document
+ * @param line the zero-based line number of the error, counted by
+ * {@code '\n'} (linefeed, {@code U+000A}) characters. Non-negative.
+ * @param pos the zero-based position of the error within the line, counted
+ * in UTF-16 code units. Non-negative.
+ * @throws IllegalArgumentException if {@code line} or {@code pos} are negative
*/
public JsonParseException(String message, int line, int pos) {
super(message);
+ if (line < 0 || pos < 0) {
+ throw new IllegalArgumentException(
+ "\"line\" and \"pos\" should be non-negative");
+ }
this.line = line;
this.pos = pos;
}
/**
- * {@return the line of the error on parsing the document}
+ * {@return the zero-based line number of the error, counted by
+ * {@code '\n'} (linefeed, {@code U+000A}) characters}
*/
public int getErrorLine() {
return line;
}
/**
- * {@return the position of the error on parsing the document}
+ * {@return the zero-based position of the error within the line,
+ * counted in UTF-16 code units}
*/
public int getErrorPosition() {
return pos;
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
index c090966b..d9175734 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonString.java
@@ -26,54 +26,60 @@
package jdk.incubator.java.util.json;
import java.util.Objects;
+
import jdk.incubator.internal.util.json.JsonStringImpl;
import jdk.incubator.internal.util.json.Utils;
/**
- * The interface that represents a JSON string.
+ * The interface that represents JSON string.
*
- * A {@code JsonString} can be produced by a {@link Json#parse(String)}.
+ * A {@code JsonString} can be produced by {@link Json#parse(String)}.
* Within a valid JSON string, any character may be escaped using either a
- * two-character escape sequence (if applicable) or a Unicode escape sequence.
- * Quotation mark (U+0022), reverse solidus (U+005C), and the control characters
- * (U+0000 through U+001F) must be escaped.
+ * two-character escape sequence (if applicable) or one or two Unicode escape
+ * sequences. A supplementary character is represented by two Unicode escape
+ * sequences corresponding to its surrogate pair.
+ * Quotation Mark (U+0022), Backslash (Reverse Solidus, U+005C), and the control
+ * characters (U+0000 through U+001F) must be escaped.
*
Alternatively, {@link #of(String)} can be used to obtain a {@code JsonString}
- * directly from a {@code String}. The {@code JsonString} instances produced by
- * the following expressions are all equivalent,
+ * directly from a {@code String}. The {@code String} values of {@code JsonString}
+ * instances produced by the following expressions are all equivalent:
* {@snippet lang = "java":
- * Json.parse("\"foo\\t\"");
- * Json.parse("\"foo\\u0009\"");
- * JsonString.of("foo\t");
+ * Json.parse("\"foo\\t\"").asString();
+ * Json.parse("\"foo\\u0009\"").asString();
+ * JsonString.of("foo\t").asString();
*}
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259#section-7 RFC 8259:
* The JavaScript Object Notation (JSON) Data Interchange Format - Strings
- * @since 99
+ * @since 28
*/
public non-sealed interface JsonString extends JsonValue {
/**
- * {@return the {@code JsonString} created from the given
- * {@code String}}
+ * {@return the {@code JsonString} created from the given {@code String}}
+ * Unlike {@link Json#parse(String)}, {@code src} is not expected to be
+ * surrounded by quotation marks and the {@linkplain ##escape-characters special characters}
+ * do not need to be escaped. As a result, {@code src} is equal to {@code
+ * JsonString.of(src).asString()}.
*
* @param src the given source {@code String}. Non-null.
* @throws NullPointerException if {@code src} is {@code null}
*/
static JsonString of(String src) {
var escaped = '"' + Utils.escape(Objects.requireNonNull(src)) + '"';
- return new JsonStringImpl(escaped.toCharArray(), 0, escaped.length(),
+ return new JsonStringImpl(escaped.toCharArray(), true, 0, escaped.length(),
escaped.length() != src.length() + 2);
}
/**
* {@return the JSON string represented by this {@code JsonString}}
- * If this {@code JsonString} was created by parsing a JSON document, it
+ * If this {@code JsonString} was created by parsing a JSON text, it
* preserves the original text representation of the corresponding JSON
- * string. Otherwise, the source {@code String} passed to the factory method
- * {@link #of(String)} is used to generate the JSON string, with special
- * characters properly escaped.
+ * string. Otherwise, the returned JSON string is the source {@code String}
+ * passed to the factory method {@link #of(String)} surrounded by double quotes
+ * with {@linkplain ##escape-characters special characters} properly escaped.
*
- * @see #string()
+ * @see #asString()
*/
@Override
String toString();
@@ -84,25 +90,5 @@ static JsonString of(String src) {
* @see #toString()
*/
@Override
- String string();
-
- /**
- * {@return true if the given {@code obj} is equal to this {@code JsonString}}
- * Two {@code JsonString}s {@code js1} and {@code js2} represent the same value
- * if {@code js1.string().equals(js2.string())}.
- *
- * @see #string()
- */
- @Override
- boolean equals(Object obj);
-
- /**
- * {@return the hash code value of this {@code JsonString}} The hash code of a
- * {@code JsonString} is derived from the hash code of {@code JsonString}'s
- * {@link #string()}.
- *
- * @see #string()
- */
- @Override
- int hashCode();
+ String asString();
}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
index 2a548c3a..43f4d197 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValue.java
@@ -24,137 +24,49 @@
*/
package jdk.incubator.java.util.json;
+
import jdk.incubator.internal.util.json.Utils;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
- * The interface that represents a JSON value. A {@code JsonValue} can be
- * produced by parsing a JSON document with {@link Json#parse(String)}. Extracting
- * a value is done in a 2-step process using {@link ##access access} and {@link
- * ##conversion conversion} methods. The {@link ##generation generation} method
- * produces the JSON compliant text from the {@code JsonValue}.
- *
Navigating JSON documents
- * Use the access methods to navigate to the desired JSON element. {@link
- * #get(String)} is provided for JSON object and {@link #element(int)} for JSON array.
- * Given the JSON document:
- * {@snippet lang=java:
- * JsonValue json = Json.parse("""
- * { "foo": ["bar", true, 42], "baz": null }
- * """);
- * }
- * the JSON String "bar" can be accessed as follows:
- * {@snippet lang=java:
- * JsonValue foo0 = json.get("foo").element(0);
- * }
- * If an access method is invoked on an incompatible JSON type (for example,
- * calling {@code get(String)} on a JSON array), a {@code JsonAssertionException}
- * is thrown.
- *
- * Once the desired JSON element is reached, call the corresponding conversion
- * method to retrieve an appropriate Java value from the {@code JsonValue}.
- *
Converting JSON values to Java values
- * Use the conversion methods to produce a Java value from the {@code
- * JsonValue}. Each conversion methods corresponds to a JSON type:
- *
- * - {@code string()} returns a String that represents the JSON string
- * with all RFC 8259 JSON escapes translated to their corresponding
- * characters.
- * - {@code toInt()} returns an int provided the JSON number is a whole
- * number within range of {@code Integer.MIN_VALUE} and
- * {@code Integer.MAX_VALUE}.
- *
- * - {@code toLong()} returns a long provided the JSON number is a whole
- * number within range of {@code Long.MIN_VALUE} and {@code Long.MAX_VALUE}.
- *
- * - {@code toDouble()} returns a double provided the JSON number is
- * within range of {@code -Double.MAX_VALUE} and {@code Double.MAX_VALUE}.
- *
- * - {@code bool()} returns {@code true} or {@code false} for JSON
- * boolean literals.
- * - {@code members()} returns an unmodifiable map of {@code String} to
- * {@code JsonValue} for JSON object, guaranteed to contain neither null
- * keys nor null values. If the JSON object contains no members, an empty
- * map is returned.
- *
- * - {@code elements()} returns an unmodifiable list of {@code JsonValue}
- * for JSON array, guaranteed to contain non-null values. If the JSON array
- * contains no elements, an empty list is returned.
- *
- * For example,
- * {@snippet lang=java:
- * String bar = foo0.string();
- * }
- * The code above retrieves the Java String "bar" from the JSON element {@code foo0}.
- * If an incorrect conversion method is used, which does not correspond to the matching
- * JSON type, for example {@code foo0.bool()}, a {@code JsonAssertionException} is thrown.
- *
- * These conversion methods always return a value when the {@code JsonValue} is
- * of the correct JSON type. The exceptions are {@code toInt()}, {@code toLong()},
- * and {@code toDouble()}; the {@code to} prefix implies that they may throw a
- * {@code JsonAssertionException} even when the {@code JsonValue} is a JSON
- * number, for example if it is outside their supported ranges.
- *
Subtypes of JsonValue
- * The {@code JsonValue} subtypes correspond to the JSON types. For example,
- * {@code JsonString} to JSON string. If the type of JSON value is unknown, it can
- * be retrieved as follows:
- * {@snippet lang=java:
- * switch (json.get("foo")) {
- * case JsonString js -> js.string(); // handle the value as JSON string
- * case JsonArray ja -> ja.element(0).string(); // handle the value as JSON array
- * default -> throw new JsonAssertionException("unexpected type");
- * }
- * }
- * Missing Object Members
- * There are times when the member in a JSON object is optional. For those
- * cases, use the access method {@link #getOrAbsent(String)} which returns an
- * Optional of JsonValue. For example:
- * {@snippet lang=java:
- * json.getOrAbsent("foo")
- * .ifPresent(IO::println)
- * }
- * This example only prints the value if the member named "foo" exists.
- * Handling of null
- * In some JSON documents, JSON null is used to signify absence.
- * For those cases, use the access method {@link #valueOrNull()} which returns an
- * Optional of JsonValue. For example:
- * {@snippet lang=java:
- * json.get("baz")
- * .valueOrNull()
- * .ifPresent(IO::println)
- * }
- * This example only prints the value if the member named "baz" is not a JSON
- * null.
- * Generating JSON documents
- * {@code JsonValue} overrides {@link Object#toString()} to generate RFC 8259 compliant
- * JSON text in a compact representation with white spaces eliminated.
- * For generating JSON documents suitable for display, use
- * the generation method {@link Json#toDisplayString(JsonValue, int)} instead.
- *
- * Instances of {@code JsonValue} are immutable and thread safe.
+ * The interface that represents a JSON value. A {@code JsonValue} represents
+ * a syntactic element within a JSON text. The {@code JsonValue} subtypes
+ * correspond to the JSON types, while {@code JsonValue} itself provides a uniform
+ * interface for navigation, conversion, and generation.
+ *
+ *
{@code JsonValue} does not define any identity or value semantics.
+ * Code that requires equality, hashing, or comparisons should use a
+ * {@linkplain jdk.incubator.java.util.json/jdk.incubator.java.util.json##conversion conversion}
+ * method to obtain a Java value upon which such operations are performed.
*
- * @implSpec A class implementing a non-sealed {@code JsonValue} sub-interface
- * must adhere to the
- * value-based
- * class requirements.
+ *
Instances of {@code JsonValue} are immutable and thread safe. See the
+ * {@linkplain jdk.incubator.java.util.json/jdk.incubator.java.util.json package specification}
+ * for an overview of parsing, accessing, converting, and generating JSON text.
*
- * @since 99
+ * @since 28
*/
public sealed interface JsonValue permits JsonString, JsonNumber, JsonObject, JsonArray, JsonBoolean, JsonNull {
/**
- * {@return the String representation of this {@code JsonValue} that conforms
- * to the JSON syntax} If this {@code JsonValue} is created by parsing a
- * JSON document, it preserves the text representation of the corresponding
- * JSON element, except that the returned string does not contain any white
- * spaces or newlines to produce a compact representation.
+ * {@return a JSON syntax conformant String representation of this {@code JsonValue}}
+ *
+ * The returned string represents the same JSON value as this object and
+ * does not contain insignificant whitespace or line separators. The returned
+ * String is not a canonical representation of the JSON value. If this {@code JsonValue}
+ * was obtained via one of the parsing methods on the {@link Json} class, the
+ * returned String is not necessarily an exact lexical match of the JSON text that
+ * was parsed. Subinterfaces may specify stronger preservation behavior for their
+ * corresponding JSON type.
+ *
* For a String representation suitable for display, use
- * {@link Json#toDisplayString(JsonValue, int)}.
+ * {@link Json#toDisplayString(JsonValue, String)}.
*
- * @see Json#toDisplayString(JsonValue, int)
+ * @see Json#toDisplayString(JsonValue, String)
*/
String toString();
@@ -163,156 +75,181 @@ public sealed interface JsonValue permits JsonString, JsonNumber, JsonObject, Js
/**
* {@return the {@code boolean} value represented by this {@code JsonValue} if
- * it is an instance of {@link JsonBoolean}}
+ * it is an instance of {@link JsonBoolean}; otherwise, throws a
+ * {@code JsonValueException}}
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonBoolean} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonBoolean} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of {@code JsonBoolean}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of {@code JsonBoolean}.
*/
- default boolean bool() {
+ default boolean asBoolean() {
throw Utils.composeTypeError(this, "JsonBoolean");
}
/**
* {@return an {@code int} if this {@code JsonValue} is an instance of {@link JsonNumber}
- * and it can be translated from its string representation} That is, it can be
- * expressed as a whole number and is within the range of
- * {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}. This occurs,
- * even if the string contains an exponent or a fractional part consisting of
- * only zero digits. For example, both the JSON number "123.0" and "1.23e2"
- * produce an {@code int} value of "123". A {@code JsonAssertionException}
+ * that can be converted exactly; otherwise, throws a {@code JsonValueException}}
+ *
+ * This {@code JsonValue} must be a JSON number that represents
+ * a whole number and that is within the range
+ * {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}, inclusive. This is true
+ * even if the JSON number contains an exponent or a fractional part consisting of
+ * all zeroes. For example, the JSON numbers "123.0" and "1.23e2" both
+ * produce an {@code int} value of {@code 123}. A {@code JsonValueException}
* is thrown when the numeric value cannot be represented as an {@code int};
- * for example, the value "5.5".
+ * for example, the JSON number "5.5".
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonNumber} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonNumber} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance
- * of {@code JsonNumber} nor can be represented as an {@code int}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance
+ * of {@code JsonNumber} or is not representable as an {@code int}.
*/
- default int toInt() {
+ default int asInt() {
throw Utils.composeTypeError(this, "JsonNumber");
}
/**
- * {@return a {@code long} if this {@code JsonValue} is an instance of {@link JsonNumber} and
- * it can be translated from its string representation} That is, it can be expressed
- * as a whole number and is within the range of {@link Long#MIN_VALUE} and
- * {@link Long#MAX_VALUE}. This occurs, even if the string contains an
- * exponent or a fractional part consisting of only zero digits. For example,
- * both the JSON number "123.0" and "1.23e2" produce a {@code long} value of
- * "123". A {@code JsonAssertionException} is thrown when the numeric value
- * cannot be represented as a {@code long}; for example, the value "5.5".
+ * {@return a {@code long} if this {@code JsonValue} is an instance of {@link JsonNumber}
+ * that can be converted exactly; otherwise, throws a {@code JsonValueException}}
+ *
+ * This {@code JsonValue} must be a JSON number that represents
+ * a whole number and that is within the range {@link Long#MIN_VALUE} to
+ * {@link Long#MAX_VALUE}, inclusive. This is true even if the JSON number contains an
+ * exponent or a fractional part consisting of all zeroes. For example,
+ * the JSON numbers "123.0" and "1.23e2" both produce a {@code long} value of
+ * {@code 123}. A {@code JsonValueException} is thrown when the numeric value
+ * cannot be represented as a {@code long}; for example, the JSON number "5.5".
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonNumber} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonNumber} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance
- * of {@code JsonNumber} nor can be represented as a {@code long}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance
+ * of {@code JsonNumber} or is not representable as a {@code long}.
*/
- default long toLong() {
+ default long asLong() {
throw Utils.composeTypeError(this, "JsonNumber");
}
/**
- * {@return a finite {@code double} if this {@code JsonValue} is an instance of
- * {@link JsonNumber} and it can be translated from its string representation}
- * If the string representation is outside the range of {@link Double#MAX_VALUE
- * -Double.MAX_VALUE} and {@link Double#MAX_VALUE}, a {@code JsonAssertionException} is thrown.
+ * {@return a {@code double} if this {@code JsonValue} is an instance of {@link JsonNumber}
+ * that can be converted, as if by {@link Double#parseDouble Double.parseDouble}, to a finite
+ * {@code double} value; otherwise, throws a {@code JsonValueException}}
+ *
+ * @apiNote Callers of this method should be aware of the potential loss in precision or
+ * magnitude when a {@code JsonNumber} is converted to a {@code double}. A JSON number
+ * may be rounded to the nearest representable {@code double} value, and a JSON number
+ * with more than about 15 decimal digits may lose precision. A JSON number with a
+ * magnitude larger than about 1.8 × 10308 cannot be
+ * represented as a finite {@code double},
+ * and attempting to convert such a number will result in {@code JsonValueException}.
+ * (This differs from {@link Double#parseDouble Double.parseDouble}, which will return
+ * {@link Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY} for such cases.)
+ * This method will never return {@link Double#NaN}. However, this method will
+ * properly convert and return negative zero ({@code -0.0}). To handle numbers of almost
+ * arbitrary precision and magnitude, consider converting to {@link java.math.BigDecimal
+ * BigDecimal} using {@code new BigDecimal(jsonNumber.toString())}. Note that
+ * {@code BigDecimal} cannot represent negative zero.
*
- * @apiNote Callers of this method should be aware of the potential loss in
- * precision when the string representation of the JSON number is translated
- * to a {@code double}.
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonNumber} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonNumber} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance
- * of {@code JsonNumber} nor can be represented as a {@code double}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance
+ * of {@code JsonNumber} or is not representable as a finite {@code double}.
*/
- default double toDouble() {
+ default double asDouble() {
throw Utils.composeTypeError(this, "JsonNumber");
}
/**
* {@return the {@code String} value represented by this {@code JsonValue} if
- * it is an instance of {@link JsonString}}
- * If this {@code JsonString} was created by parsing a JSON document, any
- * escaped characters in the original JSON document are converted to their
+ * it is an instance of {@link JsonString}; otherwise, throws a
+ * {@code JsonValueException}}
+ * If this {@code JsonString} was created by parsing a JSON text, any
+ * escaped characters in the original JSON text are converted to their
* unescaped form.
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonString} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonString} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of {@code JsonString}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of {@code JsonString}.
*/
- default String string() {
+ default String asString() {
throw Utils.composeTypeError(this, "JsonString");
}
/**
- * {@return an unmodifiable list of the {@code JsonValue} elements if this
- * {@code JsonValue} is an instance of {@link JsonArray}}
+ * {@return an unmodifiable list of the {@code JsonValue}s if this
+ * {@code JsonValue} is an instance of {@link JsonArray}; otherwise, throws a
+ * {@code JsonValueException}}
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonArray} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonArray} are expected to
* provide an implementation of this method.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of {@code JsonArray}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of {@code JsonArray}.
*/
- default List elements() {
+ default List asList() {
throw Utils.composeTypeError(this, "JsonArray");
}
/**
* {@return an unmodifiable map of {@code String} to {@code JsonValue} if this
- * {@code JsonValue} is an instance of {@link JsonObject}}
+ * {@code JsonValue} is an instance of {@link JsonObject}; otherwise, throws a
+ * {@code JsonValueException}}
*
* @implSpec
* The default implementation provided by {@code JsonValue} throws {@code
- * JsonAssertionException}. As such, implementors of {@code JsonObject} are expected to
+ * JsonValueException}. As such, implementors of {@code JsonObject} are expected to
* provide an implementation of this method.
+ * @implNote
+ * The JDK platform implementation of {@code JsonObject} preserves the
+ * encounter order of members. When a {@code JsonObject} is created by
+ * parsing, this corresponds to the order of members in the source JSON
+ * text. When created via the {@link JsonObject#of(Map)} factory method, the order
+ * follows the encounter order of the provided map.
*
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of {@code JsonObject}.
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of {@code JsonObject}.
*/
- default Map members() {
+ default Map asMap() {
throw Utils.composeTypeError(this, "JsonObject");
}
// Access methods are able to provide a suitable default implementation directly
// in JsonValue, and as such are not specified to be implemented by sub-interfaces.
// However, relevant sub-interfaces will override them to explicitly have them
- // declared in their Javadoc as well as make any specification changes.
- // valueOrNull specification would be unchanged by all sub-interfaces, and as
- // a result is left un-overridden.
+ // declared in their Javadoc as well as make any needed specification alterations.
/**
- * {@return the {@code JsonValue} associated with the given member name of a {@code JsonObject}}
+ * {@return the {@code JsonValue} associated with the given member name if this
+ * {@code JsonValue} is an instance of {@link JsonObject}; otherwise, throws a
+ * {@code JsonValueException}}
*
* @implSpec
* The default implementation obtains a {@code JsonValue} which is the result
- * of invoking {@link #members()}{@code .get(name)}. If {@code name} is absent,
- * {@code JsonAssertionException} is thrown.
+ * of invoking {@link #asMap()}{@code .get(name)}. If {@code name} is absent,
+ * {@code JsonValueException} is thrown.
*
* @param name the member name
* @throws NullPointerException if the member name is {@code null}
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of a {@code JsonObject} or
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of a {@code JsonObject} or
* there is no association with the member name
*/
default JsonValue get(String name) {
Objects.requireNonNull(name);
- return switch (members().get(name)) {
+ return switch (asMap().get(name)) {
case JsonValue jv -> jv;
case null -> throw Utils.composeError(this,
"JsonObject member \"%s\" does not exist.".formatted(name));
@@ -320,42 +257,46 @@ default JsonValue get(String name) {
}
/**
- * {@return an {@code Optional} containing the {@code JsonValue} associated with the given member
- * name of a {@code JsonObject}, otherwise if there is no association an empty {@code Optional}}
+ * {@return an {@code Optional} containing the value of a given member of
+ * this {@link JsonObject}, or an empty {@code Optional} if the member is
+ * absent; throws {@code JsonValueException} if this {@code JsonValue} is
+ * not a {@code JsonObject}}
*
* @implSpec
* The default implementation obtains an {@code Optional} by invoking {@link
- * #members()}{@code .get(name)}, which is then passed to {@link Optional#ofNullable}.
+ * #asMap()}{@code .get(name)}, which is then passed to {@link Optional#ofNullable}.
*
* @param name the member name
* @throws NullPointerException if the member name is {@code null}
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of a {@code JsonObject}
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of a {@code JsonObject}
*/
- default Optional getOrAbsent(String name) {
+ default Optional tryGet(String name) {
Objects.requireNonNull(name);
- return Optional.ofNullable(members().get(name));
+ return Optional.ofNullable(asMap().get(name));
}
/**
- * {@return the {@code JsonValue} associated with the given index of a {@code JsonArray}}
+ * {@return the {@code JsonValue} associated with the given index if this
+ * {@code JsonValue} is an instance of {@link JsonArray}; otherwise, throws a
+ * {@code JsonValueException}}
*
* @implSpec
* The default implementation obtains a {@code JsonValue} which is the result
- * of invoking {@link #elements()}{@code .get(index)}. If {@code index} is
- * out of bounds, {@code JsonAssertionException} is thrown.
+ * of invoking {@link #asList()}{@code .get(index)}. If {@code index} is
+ * out of bounds, {@code JsonValueException} is thrown.
*
* @param index the index of the array
- * @throws JsonAssertionException if this {@code JsonValue} is not an instance of a {@code JsonArray}
+ * @throws JsonValueException if this {@code JsonValue} is not an instance of a {@code JsonArray}
* or the given index is out of bounds
*/
- default JsonValue element(int index) {
- List elements = elements();
+ default JsonValue get(int index) {
+ List elements = asList();
try {
return elements.get(index);
- } catch(IndexOutOfBoundsException e) {
- throw Utils.composeError(this,
- "JsonArray index %d out of bounds for length %d."
- .formatted(index, elements.size()));
+ } catch (IndexOutOfBoundsException ignored) {
+ throw Utils.composeError(this, String.format(Locale.ROOT,
+ "JsonArray index %d out of bounds for length %d.",
+ index, elements.size()));
}
}
@@ -368,7 +309,7 @@ default JsonValue element(int index) {
* {@code JsonValue} is an instance of {@code JsonNull}; otherwise
* {@link Optional#of} given this {@code JsonValue}.
*/
- default Optional valueOrNull() {
+ default Optional tryValue() {
return switch (this) {
case JsonNull v -> Optional.empty();
case JsonValue v -> Optional.of(this);
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValueException.java b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValueException.java
new file mode 100644
index 00000000..08fac174
--- /dev/null
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/JsonValueException.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.java.util.json;
+
+import java.io.Serial;
+
+/**
+ * Indicates that an error has been detected while operating on the {@code JsonValue}.
+ * This exception is thrown under the following conditions:
+ *
+ * -
+ * An {@linkplain jdk.incubator.java.util.json/jdk.incubator.java.util.json##access access} or a
+ * {@linkplain jdk.incubator.java.util.json/jdk.incubator.java.util.json##conversion conversion} method is invoked on a
+ * {@code JsonValue} of an incompatible type. For example, calling
+ * {@code asBoolean()} on a {@code JsonString}.
+ *
+ * -
+ * An access method returning {@code JsonValue} is invoked for a non-existent
+ * value, such as {@code get(String)} for a missing member in a
+ * {@code JsonObject}, or {@code get(int)} for an out-of-bounds index in a
+ * {@code JsonArray}.
+ *
+ * -
+ * {@code asInt()} or {@code asLong()} is invoked on a {@code JsonNumber}
+ * that cannot be represented without loss of information by the target type.
+ *
+ * -
+ * {@code asDouble()} is invoked on a {@code JsonNumber} whose string
+ * representation cannot be converted to a finite {@code double}.
+ *
+ *
+ * @since 28
+ */
+public final class JsonValueException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 2040280066622450939L;
+
+ /**
+ * Constructs a {@code JsonValueException} with the specified detail message.
+ * @param message the detail message
+ */
+ public JsonValueException(String message) {
+ super(message);
+ }
+}
diff --git a/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java b/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
index 8b54d5bc..d612ba02 100644
--- a/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
+++ b/json-java21/src/main/java/jdk/incubator/java/util/json/package-info.java
@@ -24,45 +24,162 @@
*/
/**
- * Provides APIs for parsing JSON text, retrieving JSON values in the text, and
- * generating JSON text.
+ * This API supports processing of JSON text in a simple manner. It is organized around the {@link
+ * JsonValue} interface which represents a JSON value, and the {@link Json} class which provides
+ * methods to parse and generate JSON text. Typical usage of this API involves first
+ * {@linkplain ##parsing parsing} JSON text into a {@code JsonValue}, {@linkplain ##access navigating}
+ * the parsed JSON value to the desired JSON value using access methods, and lastly
+ * {@linkplain ##conversion converting} the desired value using a conversion method.
+ * For example:
+ * {@snippet lang = java:
+ * List providers = Json.parse(text)
+ * .get("providers") // access
+ * .asList(); // conversion
+ * }
*
- * Parsing JSON documents
+ * Parsing JSON text
+ * Parsing JSON text can be done using either {@link Json#parse(java.lang.String)} or {@link Json#parse(char[])}.
+ * {@snippet lang = java:
+ * JsonValue json = Json.parse(text);
+ * }
+ * A successful parse indicates that the JSON text adheres to the JSON grammar.
+ * Unsuccessful parsing throws a {@link JsonParseException}, which provides a detail message that includes
+ * error details, a path to the root of the JSON text, and its location within the text.
+ * The parsing APIs do not accept JSON text that contains JSON objects with duplicate member names.
+ *
+ * The result of a successful parse is a {@code JsonValue}. The {@code JsonValue} interface has six
+ * sub-interfaces: {@link JsonString}, {@link JsonNumber}, {@link JsonBoolean}, {@link JsonNull},
+ * {@link JsonObject}, and {@link JsonArray}. Each sub-interface corresponds to one of the elements of
+ * JSON syntax. This type hierarchy allows you to use pattern matching to determine the subtype
+ * of a {@code JsonValue}. {@code JsonValue} instances are immutable and thread safe.
*
- * Parsing produces a {@code JsonValue} from JSON text and is done using either
- * {@link Json#parse(java.lang.String)} or {@link Json#parse(char[])}. A successful
- * parse indicates that the JSON text adheres to the
- * JSON grammar.
- * The parsing APIs provided do not accept JSON text that contain JSON objects
- * with duplicate names.
+ *
Navigating JSON text
+ * Once you have obtained a {@code JsonValue} from parsing, use the access methods to navigate
+ * through JSON structural elements. {@link JsonValue#get(String)} is provided for JSON objects and {@link
+ * JsonValue#get(int)} for JSON arrays.
+ * Given the JSON text:
+ * {@snippet lang=java:
+ * JsonValue json = Json.parse("""
+ * { "providers": [ "SUN", "SunRsaSign", "SunEC" ], "version": 1 }
+ * """);
+ * }
+ * the JSON string "SUN" can be accessed as follows:
+ * {@snippet lang=java:
+ * JsonValue firstProvider = json.get("providers").get(0);
+ * }
+ * If an access method is invoked on an incompatible JSON type, for example,
+ * calling {@code get(String)} on a JSON array, a {@link JsonValueException}
+ * is thrown.
*
- * Retrieving JSON values
+ * Handling optional members
+ * A member of a JSON object can be optional. In this scenario, use the access method
+ * {@link JsonValue#tryGet(String)} which returns an {@code Optional} of {@code JsonValue}.
+ * For example:
+ * {@snippet lang=java:
+ * json.tryGet("providers")
+ * .ifPresent(IO::println);
+ * }
+ * This example only prints the value if the member named "providers" exists.
*
- * Retrieving values from a JSON document involves two steps: first navigating
- * the document structure using a chain of "access" methods, and then converting
- * the result to the desired type using a "conversion" method. For example,
+ * Handling null values
+ * Sometimes, JSON null is used to signify absence of a member.
+ * In this scenario, use the access method {@link JsonValue#tryValue()} which returns an
+ * {@code Optional} of {@code JsonValue}. For example:
* {@snippet lang=java:
- * var name = doc.get("foo").get("bar").element(0).string();
+ * json.get("providers")
+ * .tryValue()
+ * .ifPresent(IO::println);
* }
- * By chaining access methods, the "foo" member is retrieved from the root object,
- * then the "bar" member from "foo", followed by the element at index 0 from "bar".
- * The navigation process leads to a leaf JSON string element. The final call to the
- * {@code string()} conversion method returns the corresponding String object. For more
- * details on these methods, see {@link JsonValue JsonValue}.
+ * This example only prints the value if the member named "providers" is not a JSON
+ * null.
*
- * Generating JSON documents
+ * Handling variance in type or structure
+ * If the type for a JSON value is variable, it can be handled as follows:
+ * {@snippet lang = java:
+ * String firstProvider = switch (json.get("providers")) {
+ * case JsonString js -> js.asString(); // handle the value as JSON string
+ * case JsonArray ja -> ja.get(0).asString(); // handle the value as JSON array
+ * default -> throw new JsonValueException("unexpected type");
+ * }
+ * }
+ * While the code above throws an exception if the type is neither {@code JsonString} or
+ * {@code JsonArray}, there are times when you may prefer a fallback value instead.
+ * For example:
+ * {@snippet lang = java:
+ * String firstProvider = Optional.of(json)
+ * .filter(j -> j instanceof JsonObject)
+ * .flatMap(j -> j.tryGet("providers"))
+ * .filter(j -> j instanceof JsonString)
+ * .map(JsonValue::asString)
+ * .orElse("none");
+ * }
+ * This code ensures that if the root JSON value is not an object,
+ * the member "providers" does not exist, or if the value of "providers" is not a JSON String,
+ * then the {@code "none"} fallback value is used over throwing an exception.
+ *
+ * Converting JSON values to Java values
+ * Once you have navigated to your desired {@code JsonValue}, use the conversion methods to produce
+ * a corresponding Java value. Each conversion method requires a particular JSON type:
+ *
+ * - {@link JsonValue#asString() asString()} converts a {@code JsonString} instance into a Java
+ * {@code String} with RFC 8259 JSON escape sequences translated to their
+ * corresponding characters.
+ * - {@link JsonValue#asInt() asInt()} converts a {@code JsonNumber} instance to a Java
+ * {@code int} if its numeric value can be represented exactly.
+ * - {@link JsonValue#asLong() asLong()} converts a {@code JsonNumber} instance to a Java
+ * {@code long} if its numeric value can be represented exactly.
+ * - {@link JsonValue#asDouble() asDouble()} converts a {@code JsonNumber} instance to a Java
+ * {@code double} if its numeric value can be rounded to a finite Java {@code double}.
+ * - {@link JsonValue#asBoolean() asBoolean()} converts a {@code JsonBoolean} instance to a Java
+ * {@code boolean} value of {@code true} or {@code false}.
+ * - {@link JsonValue#asMap() asMap()} converts a {@code JsonObject} instance into an
+ * unmodifiable Java {@code Map}. If the JSON object contains no members, an
+ * empty {@code Map} is returned.
+ * - {@link JsonValue#asList() asList()} converts a {@code JsonArray} instance into an
+ * unmodifiable Java {@code List}. If the JSON array contains no elements,
+ * an empty {@code List} is returned.
+ *
+ * For example:
+ * {@snippet lang=java:
+ * String sun = firstProvider.asString();
+ * }
+ * The code above retrieves the Java String {@code "SUN"} from the JSON value {@code firstProvider}.
+ * If an incorrect conversion method is used, which does not correspond to the matching
+ * JSON type, for example {@code firstProvider.asBoolean()}, a {@code JsonValueException} is thrown.
+ *
+ * Most conversion methods always return a value when the {@code JsonValue} is
+ * of the correct JSON type. The exceptions are {@code asInt()}, {@code asLong()},
+ * and {@code asDouble()}; they may throw a {@code JsonValueException} even
+ * when the {@code JsonValue} is a JSON number, for example if it is outside
+ * their supported ranges.
*
+ *
Generating JSON text
* Generating JSON text is performed with either {@link
- * JsonValue#toString()} or {@link Json#toDisplayString(JsonValue, int)}.
- * These methods produce formatted String representations of a {@code JsonValue}.
- * The returned text adheres to the JSON grammar defined in RFC 8259.
- * {@code JsonValue.toString()} produces the most compact representation which does not
- * include extra whitespaces or line-breaks, preferable for network transaction
- * or storage. {@code Json.toDisplayString(JsonValue, int)} produces a text which
- * is human friendly, preferable for debugging or logging.
+ * JsonValue#toString()} or {@link Json#toDisplayString(JsonValue, String)}.
+ * These methods produce String representations of a {@code JsonValue} that adhere
+ * to the JSON grammar defined in RFC 8259.
+ * {@code JsonValue.toString()} produces compact JSON text which does not
+ * include JSON insignificant whitespace, preferable for network transmission
+ * or storage. For example:
+ * {@snippet lang=json:
+ * {"providers":["SUN","SunRsaSign","SunEC"],"version":1}
+ * }
+ * {@code Json.toDisplayString(JsonValue, String)} produces pretty-printed
+ * JSON text which is easier to read, preferable for debugging or logging.
+ * For example:
+ * {@snippet lang=json:
+ * {
+ * "providers": [
+ * "SUN",
+ * "SunRsaSign",
+ * "SunEC"
+ * ],
+ * "version": 1
+ * }
+ * }
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259 RFC 8259: The JavaScript
* Object Notation (JSON) Data Interchange Format
- * @since 99
+ * @since 28
*/
package jdk.incubator.java.util.json;
diff --git a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
index 4aba2b58..4feb7043 100644
--- a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonParserTests.java
@@ -14,24 +14,24 @@ public class JsonParserTests {
void testParseComplexJson() {
JsonObject jsonObject = complexJsonObject();
- assertThat(((JsonString) jsonObject.members().get("name")).string()).isEqualTo("John Doe");
- assertThat(((JsonNumber) jsonObject.members().get("age")).toLong()).isEqualTo(30L);
- assertThat(((JsonBoolean) jsonObject.members().get("isStudent")).bool()).isFalse();
+ assertThat(((JsonString) jsonObject.asMap().get("name")).asString()).isEqualTo("John Doe");
+ assertThat(((JsonNumber) jsonObject.asMap().get("age")).asLong()).isEqualTo(30L);
+ assertThat(((JsonBoolean) jsonObject.asMap().get("isStudent")).asBoolean()).isFalse();
- JsonArray courses = (JsonArray) jsonObject.members().get("courses");
- assertThat(courses.elements()).hasSize(2);
+ JsonArray courses = (JsonArray) jsonObject.asMap().get("courses");
+ assertThat(courses.asList()).hasSize(2);
- JsonObject course1 = (JsonObject) courses.elements().getFirst();
- assertThat(((JsonString) course1.members().get("title")).string()).isEqualTo("History");
- assertThat(((JsonNumber) course1.members().get("credits")).toLong()).isEqualTo(3L);
+ JsonObject course1 = (JsonObject) courses.asList().getFirst();
+ assertThat(((JsonString) course1.asMap().get("title")).asString()).isEqualTo("History");
+ assertThat(((JsonNumber) course1.asMap().get("credits")).asLong()).isEqualTo(3L);
- JsonObject course2 = (JsonObject) courses.elements().get(1);
- assertThat(((JsonString) course2.members().get("title")).string()).isEqualTo("Math");
- assertThat(((JsonNumber) course2.members().get("credits")).toLong()).isEqualTo(4L);
+ JsonObject course2 = (JsonObject) courses.asList().get(1);
+ assertThat(((JsonString) course2.asMap().get("title")).asString()).isEqualTo("Math");
+ assertThat(((JsonNumber) course2.asMap().get("credits")).asLong()).isEqualTo(4L);
- JsonObject address = (JsonObject) jsonObject.members().get("address");
- assertThat(((JsonString) address.members().get("street")).string()).isEqualTo("123 Main St");
- assertThat(((JsonString) address.members().get("city")).string()).isEqualTo("Anytown");
+ JsonObject address = (JsonObject) jsonObject.asMap().get("address");
+ assertThat(((JsonString) address.asMap().get("street")).asString()).isEqualTo("123 Main St");
+ assertThat(((JsonString) address.asMap().get("city")).asString()).isEqualTo("Anytown");
}
private static JsonObject complexJsonObject() {
diff --git a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
index d03e3f5a..aacd5272 100644
--- a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonPatternMatchingTests.java
@@ -15,11 +15,11 @@ public class JsonPatternMatchingTests {
private String identifyJsonValue(JsonValue jsonValue) {
return switch (jsonValue) {
- case JsonObject o -> "Object with " + o.members().size() + " members";
- case JsonArray a -> "Array with " + a.elements().size() + " elements";
- case JsonString s -> "String with value: " + s.string();
- case JsonNumber n -> "Number with value: " + n.toDouble();
- case JsonBoolean b -> "Boolean with value: " + b.bool();
+ case JsonObject o -> "Object with " + o.asMap().size() + " members";
+ case JsonArray a -> "Array with " + a.asList().size() + " elements";
+ case JsonString s -> "String with value: " + s.asString();
+ case JsonNumber n -> "Number with value: " + n.asDouble();
+ case JsonBoolean b -> "Boolean with value: " + b.asBoolean();
case JsonNull ignored -> "Null";
};
}
@@ -40,11 +40,11 @@ void testPatternMatchingOnJsonTypes() {
JsonParser parser = new JsonParser(json.toCharArray());
JsonObject jsonObject = (JsonObject) parser.parseRoot();
- assertThat(identifyJsonValue(jsonObject.members().get("myObject"))).isEqualTo("Object with 0 members");
- assertThat(identifyJsonValue(jsonObject.members().get("myArray"))).isEqualTo("Array with 2 elements");
- assertThat(identifyJsonValue(jsonObject.members().get("myString"))).isEqualTo("String with value: hello");
- assertThat(identifyJsonValue(jsonObject.members().get("myNumber"))).isEqualTo("Number with value: 123.45");
- assertThat(identifyJsonValue(jsonObject.members().get("myBoolean"))).isEqualTo("Boolean with value: true");
- assertThat(identifyJsonValue(jsonObject.members().get("myNull"))).isEqualTo("Null");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myObject"))).isEqualTo("Object with 0 members");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myArray"))).isEqualTo("Array with 2 elements");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myString"))).isEqualTo("String with value: hello");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myNumber"))).isEqualTo("Number with value: 123.45");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myBoolean"))).isEqualTo("Boolean with value: true");
+ assertThat(identifyJsonValue(jsonObject.asMap().get("myNull"))).isEqualTo("Null");
}
}
diff --git a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
index 93692eb1..a7e5c7a2 100644
--- a/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
+++ b/json-java21/src/test/java/jdk/incubator/internal/util/json/JsonRecordMappingTests.java
@@ -76,32 +76,32 @@ private Ecommerce toDomain(JsonValue jsonValue) {
throw new IllegalArgumentException("Expected a JsonObject");
}
- Map members = jsonObject.members();
- String type = ((JsonString) members.get("type")).string();
+ Map members = jsonObject.asMap();
+ String type = ((JsonString) members.get("type")).asString();
return switch (type) {
case "order" -> {
- String orderId = ((JsonString) members.get("orderId")).string();
+ String orderId = ((JsonString) members.get("orderId")).asString();
Customer customer = (Customer) toDomain(members.get("customer"));
- List items = ((JsonArray) members.get("items")).elements().stream()
+ List items = ((JsonArray) members.get("items")).asList().stream()
.map(item -> (LineItem) toDomain(item))
.collect(Collectors.toList());
yield new Order(orderId, customer, items);
}
case "customer" -> {
- String name = ((JsonString) members.get("name")).string();
- String email = ((JsonString) members.get("email")).string();
+ String name = ((JsonString) members.get("name")).asString();
+ String email = ((JsonString) members.get("email")).asString();
yield new Customer(name, email);
}
case "lineItem" -> {
Product product = (Product) toDomain(members.get("product"));
- int quantity = (int) ((JsonNumber) members.get("quantity")).toLong();
+ int quantity = (int) ((JsonNumber) members.get("quantity")).asLong();
yield new LineItem(product, quantity);
}
case "product" -> {
- String sku = ((JsonString) members.get("sku")).string();
- String name = ((JsonString) members.get("name")).string();
- double price = ((JsonNumber) members.get("price")).toDouble();
+ String sku = ((JsonString) members.get("sku")).asString();
+ String name = ((JsonString) members.get("name")).asString();
+ double price = ((JsonNumber) members.get("price")).asDouble();
yield new Product(sku, name, price);
}
default -> throw new IllegalStateException("Unexpected value: " + type);
From 2afc50beb84c7a7cfe23a0ccf619c802fc263aae Mon Sep 17 00:00:00 2001
From: Simon Massey <322608+simbo1905@users.noreply.github.com>
Date: Sun, 30 Aug 2026 07:36:40 +0100
Subject: [PATCH 6/9] Issue #145 apply upstream API renames across modules
Global rename per upstream 43325738c: bool->asBoolean, string->asString,
toInt/toLong/toDouble->asInt/asLong/asDouble, elements->asList,
members->asMap, element(int)->get(int), getOrAbsent->tryGet,
valueOrNull->tryValue, JsonAssertionException->JsonValueException;
toDisplayString indent int->String at all call sites; method references
(JsonValue::asString etc.) and jtd-codegen emitted bytecode method names
updated. Record accessors (Team.members, ElementsSchema.elements) kept.
---
AGENTS.md | 4 +-
README.md | 42 ++---
index.html | 6 +-
.../JsonCompatibilitySummary.java | 2 +-
.../github/simbo1905/tracker/ApiTracker.java | 146 +++++++++---------
.../simbo1905/tracker/ApiTrackerRunner.java | 2 +-
.../simbo1905/tracker/ApiTrackerTest.java | 46 +++---
.../src/test/resources/JsonObject.java | 2 +-
.../java/json/java21/jsonpath/JsonPath.java | 36 ++---
.../json/java21/jsonpath/JsonPathStreams.java | 16 +-
.../JsonPathFilterEvaluationTest.java | 2 +-
.../java21/jsonpath/JsonPathGoessnerTest.java | 48 +++---
.../java21/jtd/codegen/EmitDiscriminator.java | 8 +-
.../json/java21/jtd/codegen/EmitElements.java | 4 +-
.../json/java21/jtd/codegen/EmitEnum.java | 2 +-
.../java21/jtd/codegen/EmitProperties.java | 4 +-
.../json/java21/jtd/codegen/EmitType.java | 6 +-
.../json/java21/jtd/codegen/EmitValues.java | 4 +-
.../codegen/CodegenSpecConformanceTest.java | 20 +--
json-java21-jtd/JTD_CODEGEN_SPEC.md | 2 +-
.../json/java21/jtd/InterpreterValidator.java | 20 +--
.../src/main/java/json/java21/jtd/Jtd.java | 98 ++++++------
.../main/java/json/java21/jtd/JtdSchema.java | 32 ++--
.../java/json/java21/jtd/JtdPropertyTest.java | 22 +--
.../java21/jtd/JtdSpecConformanceTest.java | 20 +--
.../java/json/java21/jtd/TestRfc8927.java | 14 +-
.../java/util/json/EscapedKeyBugTest.java | 10 +-
.../java/util/json/ReadmeDemoTests.java | 48 +++---
.../util/json/TestJsonNumberOfDouble.java | 8 +-
.../util/json/examples/ReadmeExamples.java | 24 +--
30 files changed, 349 insertions(+), 349 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index d55c8a6b..8e8867eb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -124,7 +124,7 @@ throw new IllegalArgumentException("enum contains duplicate values: " +
// Include the problematic schema portion
throw new IllegalArgumentException("Type schema contains unknown key: " + key +
- " in schema: " + Json.toDisplayString(obj, 0));
+ " in schema: " + Json.toDisplayString(obj, ""));
// Include both expected and actual values
throw new IllegalArgumentException("unknown type: '" + typeStr +
@@ -138,7 +138,7 @@ throw new IllegalArgumentException("invalid schema"); // Too vague
throw new IllegalArgumentException("bad value"); // No specifics
```
-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.
+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.
## JSON Compatibility Suite
diff --git a/README.md b/README.md
index 61de57c0..83ab0096 100644
--- a/README.md
+++ b/README.md
@@ -60,9 +60,9 @@ JsonValue value = Json.parse(json);
// Access as map-like structure
JsonObject obj = (JsonObject) 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();
+String name = ((JsonString) obj.asMap().get("name")).asString();
+long age = ((JsonNumber) obj.asMap().get("age")).asLong();
+boolean active = ((JsonBoolean) obj.asMap().get("active")).asBoolean();
```
### Simple Record Mapping
@@ -77,9 +77,9 @@ JsonObject jsonObj = (JsonObject) Json.parse(userJson);
// Map to record
User user = new User(
- ((JsonString) jsonObj.members().get("name")).string(),
- ((JsonNumber) jsonObj.members().get("age")).toLong(),
- ((JsonBoolean) jsonObj.members().get("active")).bool()
+ ((JsonString) jsonObj.asMap().get("name")).asString(),
+ ((JsonNumber) jsonObj.asMap().get("age")).asLong(),
+ ((JsonBoolean) jsonObj.asMap().get("active")).asBoolean()
);
// Convert records back to JSON using typed factories
@@ -117,9 +117,9 @@ JsonValue parsed = Json.parse("{\"name\":\"John\",\"age\":30}");
JsonObject obj = (JsonObject) parsed;
// 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
+String name = obj.get("name").asString(); // Returns "John"
+long age = obj.get("age").asLong(); // Returns 30L
+double ageDouble = obj.get("age").asDouble(); // Returns 30.0
```
The accessor methods on `JsonValue`:
@@ -162,14 +162,14 @@ JsonValue teamJson = JsonObject.of(Map.of(
// Parse JSON back to records
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
Team reconstructed = new Team(
- ((JsonString) parsed.members().get("teamName")).string(),
- ((JsonArray) parsed.members().get("members")).elements().stream()
+ ((JsonString) parsed.asMap().get("teamName")).asString(),
+ ((JsonArray) parsed.asMap().get("members")).asList().stream()
.map(v -> {
JsonObject member = (JsonObject) v;
return new User(
- ((JsonString) member.members().get("name")).string(),
- ((JsonString) member.members().get("email")).string(),
- ((JsonBoolean) member.members().get("active")).bool()
+ ((JsonString) member.asMap().get("name")).asString(),
+ ((JsonString) member.asMap().get("email")).asString(),
+ ((JsonBoolean) member.asMap().get("active")).asBoolean()
);
})
.toList()
@@ -206,10 +206,10 @@ Process JSON arrays efficiently with Java streams:
```java
// Filter active users from a JSON array
JsonArray users = (JsonArray) Json.parse(jsonArrayString);
-List activeUserEmails = users.elements().stream()
+List activeUserEmails = users.asList().stream()
.map(v -> (JsonObject) v)
- .filter(obj -> ((JsonBoolean) obj.members().get("active")).bool())
- .map(obj -> ((JsonString) obj.members().get("email")).string())
+ .filter(obj -> ((JsonBoolean) obj.asMap().get("active")).asBoolean())
+ .map(obj -> ((JsonString) obj.asMap().get("email")).asString())
.toList();
```
@@ -242,7 +242,7 @@ JsonObject data = JsonObject.of(Map.of(
))
));
-String formatted = Json.toDisplayString(data, 2);
+String formatted = Json.toDisplayString(data, " ");
// Output:
// {
// "name": "Alice",
@@ -296,7 +296,7 @@ This code is derived from the OpenJDK jdk-sandbox repository "json" branch at co
- `JsonValue` navigation methods: `get(String)`, `get(int)`, `getOrAbsent(String)`, `valueOrNull()`
- `JsonArray`: `elements()`, `of(List)`
- `JsonObject`: `members()`, `of(Map)`
-- `Json`: `parse(String)`, `parse(char[])`, `toDisplayString(JsonValue, int)`
+- `Json`: `parse(String)`, `parse(char[])`, `toDisplayString(JsonValue, String indent)`
### Upstream Migration Notice
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.
@@ -415,7 +415,7 @@ JsonValue doc = Json.parse("""
var authors = JsonPath.parse("$.store.book[*].author")
.query(doc)
.stream()
- .map(JsonValue::string)
+ .map(JsonValue::asString)
.toList();
System.out.println("Authors count: " + authors.size()); // prints '3'
@@ -425,7 +425,7 @@ System.out.println("Last author: " + authors.getLast()); // prints 'Marek Ily
var cheapTitles = JsonPath.parse("$.store.book[?(@.price < 10)].title")
.query(doc)
.stream()
- .map(JsonValue::string)
+ .map(JsonValue::asString)
.toList();
var priceStats = JsonPath.parse("$.store.book[*].price")
diff --git a/index.html b/index.html
index cf463db5..46660e79 100644
--- a/index.html
+++ b/index.html
@@ -183,8 +183,8 @@ Parse and access
JsonObject obj = (JsonObject) Json.parse("{\"name\":\"Alice\",\"age\":30}");
-String name = obj.get("name").string();
-long age = obj.get("age").toLong();
+String name = obj.get("name").asString();
+long age = obj.get("age").asLong();
@@ -192,7 +192,7 @@ What’s Included
- Immutable JSON values:
JsonObject, JsonArray, JsonString, JsonNumber, JsonBoolean, JsonNull
- Typed factories: build JSON with
JsonObject.of, JsonArray.of, JsonString.of, JsonNumber.of, JsonBoolean.of
- - Type-safe accessors:
obj.get("x").string(), value.toLong(), value.toDouble(), value.bool()
+ - Type-safe accessors:
obj.get("x").asString(), value.asLong(), value.asDouble(), value.asBoolean()
- JTD validator (RFC 8927): module
json-java21-jtd for real-world JSON-heavy logic
diff --git a/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
index 3557ab54..15fecb51 100644
--- a/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
+++ b/json-compatibility-suite/src/main/java/jdk/incubator/compatibility/JsonCompatibilitySummary.java
@@ -127,7 +127,7 @@ void generateJsonReport() throws Exception {
LOGGER.fine(() -> "Starting JSON report generation");
TestResults results = runTests();
JsonObject report = createJsonReport(results);
- System.out.println(Json.toDisplayString(report, 2));
+ System.out.println(Json.toDisplayString(report, " "));
}
private TestResults runTests() throws Exception {
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
index 05a17ef9..fb653dc6 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
@@ -597,22 +597,22 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
final var diffMap = new LinkedHashMap();
// Extract class name safely
- final var localClassName = local.members().get("className");
+ final var localClassName = local.asMap().get("className");
final var className = localClassName instanceof JsonString js ?
- js.string() : "Unknown";
+ js.asString() : "Unknown";
diffMap.put("className", JsonString.of(className));
// Check for upstream errors
- if (upstream.members().containsKey("error")) {
+ if (upstream.asMap().containsKey("error")) {
diffMap.put("status", JsonString.of("UPSTREAM_ERROR"));
- diffMap.put("error", upstream.members().get("error"));
+ diffMap.put("error", upstream.asMap().get("error"));
return JsonObject.of(diffMap);
}
// Check if status is NOT_IMPLEMENTED (from parsing)
- if (upstream.members().containsKey("status")) {
- final var status = ((JsonString) upstream.members().get("status")).string();
+ if (upstream.asMap().containsKey("status")) {
+ final var status = ((JsonString) upstream.asMap().get("status")).asString();
if ("NOT_IMPLEMENTED".equals(status)) {
diffMap.put("status", JsonString.of("PARSE_NOT_IMPLEMENTED"));
return JsonObject.of(diffMap);
@@ -657,8 +657,8 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
/// Compares a simple boolean attribute
static boolean compareAttribute(String attrName, JsonObject local, JsonObject upstream, List differences) {
- final var localValue = local.members().get(attrName);
- final var upstreamValue = upstream.members().get(attrName);
+ final var localValue = local.asMap().get(attrName);
+ final var upstreamValue = upstream.asMap().get(attrName);
if (!Objects.equals(localValue, upstreamValue)) {
differences.add(JsonObject.of(Map.of(
@@ -674,18 +674,18 @@ static boolean compareAttribute(String attrName, JsonObject local, JsonObject up
/// Compares class modifiers
static boolean compareModifiers(JsonObject local, JsonObject upstream, List differences) {
- final var localMods = (JsonArray) local.members().get("modifiers");
- final var upstreamMods = (JsonArray) upstream.members().get("modifiers");
+ final var localMods = (JsonArray) local.asMap().get("modifiers");
+ final var upstreamMods = (JsonArray) upstream.asMap().get("modifiers");
if (localMods == null || upstreamMods == null) {
return false;
}
- final var localSet = localMods.elements().stream()
- .map(v -> ((JsonString) v).string())
+ final var localSet = localMods.asList().stream()
+ .map(v -> ((JsonString) v).asString())
.collect(Collectors.toSet());
- final var upstreamSet = upstreamMods.elements().stream()
- .map(v -> ((JsonString) v).string())
+ final var upstreamSet = upstreamMods.asList().stream()
+ .map(v -> ((JsonString) v).asString())
.collect(Collectors.toSet());
if (!localSet.equals(upstreamSet)) {
@@ -701,18 +701,18 @@ static boolean compareModifiers(JsonObject local, JsonObject upstream, List differences) {
- final var localExtends = (JsonArray) local.members().get("extends");
- final var upstreamExtends = (JsonArray) upstream.members().get("extends");
+ final var localExtends = (JsonArray) local.asMap().get("extends");
+ final var upstreamExtends = (JsonArray) upstream.asMap().get("extends");
if (localExtends == null || upstreamExtends == null) {
return false;
}
- final var localTypes = localExtends.elements().stream()
- .map(v -> normalizeTypeName(((JsonString) v).string()))
+ final var localTypes = localExtends.asList().stream()
+ .map(v -> normalizeTypeName(((JsonString) v).asString()))
.collect(Collectors.toSet());
- final var upstreamTypes = upstreamExtends.elements().stream()
- .map(v -> normalizeTypeName(((JsonString) v).string()))
+ final var upstreamTypes = upstreamExtends.asList().stream()
+ .map(v -> normalizeTypeName(((JsonString) v).asString()))
.collect(Collectors.toSet());
if (!localTypes.equals(upstreamTypes)) {
@@ -728,8 +728,8 @@ static boolean compareInheritance(JsonObject local, JsonObject upstream, List differences) {
- final var localMethods = (JsonObject) local.members().get("methods");
- final var upstreamMethods = (JsonObject) upstream.members().get("methods");
+ final var localMethods = (JsonObject) local.asMap().get("methods");
+ final var upstreamMethods = (JsonObject) upstream.asMap().get("methods");
if (localMethods == null || upstreamMethods == null) {
return false;
@@ -738,8 +738,8 @@ static boolean compareMethods(JsonObject local, JsonObject upstream, List differences) {
- final var localFields = (JsonObject) local.members().get("fields");
- final var upstreamFields = (JsonObject) upstream.members().get("fields");
+ final var localFields = (JsonObject) local.asMap().get("fields");
+ final var upstreamFields = (JsonObject) upstream.asMap().get("fields");
if (localFields == null || upstreamFields == null) {
return false;
@@ -824,8 +824,8 @@ static boolean compareFields(JsonObject local, JsonObject upstream, List differences) {
- final var localConstructors = (JsonArray) local.members().get("constructors");
- final var upstreamConstructors = (JsonArray) upstream.members().get("constructors");
+ final var localConstructors = (JsonArray) local.asMap().get("constructors");
+ final var upstreamConstructors = (JsonArray) upstream.asMap().get("constructors");
if (localConstructors == null || upstreamConstructors == null) {
return false;
}
- if (localConstructors.elements().size() != upstreamConstructors.elements().size()) {
+ if (localConstructors.asList().size() != upstreamConstructors.asList().size()) {
differences.add(JsonObject.of(Map.of(
"type", JsonString.of("constructorsChanged"),
- "localCount", JsonNumber.of(localConstructors.elements().size()),
- "upstreamCount", JsonNumber.of(upstreamConstructors.elements().size())
+ "localCount", JsonNumber.of(localConstructors.asList().size()),
+ "upstreamCount", JsonNumber.of(upstreamConstructors.asList().size())
)));
return true;
}
@@ -908,7 +908,7 @@ static JsonObject runFullComparison() {
differences.add(diff);
// Count statistics
- final var status = ((JsonString) diff.members().get("status")).string();
+ final var status = ((JsonString) diff.asMap().get("status")).asString();
switch (status) {
case "MATCHING" -> matchingCount++;
case "UPSTREAM_ERROR" -> missingUpstream++;
@@ -961,24 +961,24 @@ static String generateFingerprint(JsonObject report) {
}
// Build a stable, sorted representation of just the essential diff info
- final var differences = (JsonArray) report.members().get("differences");
+ final var differences = (JsonArray) report.asMap().get("differences");
final var stableLines = new ArrayList();
- for (final var diff : differences.elements()) {
+ for (final var diff : differences.asList()) {
final var diffObj = (JsonObject) diff;
- final var status = ((JsonString) diffObj.members().get("status")).string();
+ final var status = ((JsonString) diffObj.asMap().get("status")).asString();
if (!"DIFFERENT".equals(status)) continue;
- final var className = ((JsonString) diffObj.members().get("className")).string();
- final var classDiffs = (JsonArray) diffObj.members().get("differences");
+ final var className = ((JsonString) diffObj.asMap().get("className")).asString();
+ final var classDiffs = (JsonArray) diffObj.asMap().get("differences");
if (classDiffs != null) {
- for (final var change : classDiffs.elements()) {
+ for (final var change : classDiffs.asList()) {
final var changeObj = (JsonObject) change;
- final var type = ((JsonString) changeObj.members().get("type")).string();
- final var methodValue = changeObj.members().get("method");
- final var method = methodValue instanceof JsonString js ? js.string() : "";
+ final var type = ((JsonString) changeObj.asMap().get("type")).asString();
+ final var methodValue = changeObj.asMap().get("method");
+ final var method = methodValue instanceof JsonString js ? js.asString() : "";
// Create stable line: "ClassName:changeType:methodName"
stableLines.add(className + ":" + type + ":" + method);
}
@@ -1007,13 +1007,13 @@ static String generateFingerprint(JsonObject report) {
/// @param report the comparison report
/// @return the count of classes with different APIs
private static long getDifferentApiCount(JsonObject report) {
- final var summary = (JsonObject) report.members().get("summary");
+ final var summary = (JsonObject) report.asMap().get("summary");
if (summary == null) {
return 0;
}
- final var differentApiValue = summary.members().get("differentApi");
+ final var differentApiValue = summary.asMap().get("differentApi");
if (differentApiValue instanceof JsonNumber num) {
- return num.toLong();
+ return num.asLong();
}
return 0;
}
@@ -1024,13 +1024,13 @@ private static long getDifferentApiCount(JsonObject report) {
/// @return markdown-formatted summary
static String generateSummary(JsonObject report) {
final var sb = new StringBuilder();
- final var summary = (JsonObject) report.members().get("summary");
- final var differences = (JsonArray) report.members().get("differences");
+ final var summary = (JsonObject) report.asMap().get("summary");
+ final var differences = (JsonArray) report.asMap().get("differences");
- final var totalClasses = ((JsonNumber) summary.members().get("totalClasses")).toLong();
- final var matchingClasses = ((JsonNumber) summary.members().get("matchingClasses")).toLong();
+ final var totalClasses = ((JsonNumber) summary.asMap().get("totalClasses")).asLong();
+ final var matchingClasses = ((JsonNumber) summary.asMap().get("matchingClasses")).asLong();
final var differentApi = getDifferentApiCount(report);
- final var missingUpstream = ((JsonNumber) summary.members().get("missingUpstream")).toLong();
+ final var missingUpstream = ((JsonNumber) summary.asMap().get("missingUpstream")).asLong();
sb.append("## API Comparison Summary\n\n");
sb.append("| Metric | Count |\n");
@@ -1043,22 +1043,22 @@ static String generateSummary(JsonObject report) {
if (differentApi > 0) {
sb.append("## Changes Detected\n\n");
- for (final var diff : differences.elements()) {
+ for (final var diff : differences.asList()) {
final var diffObj = (JsonObject) diff;
- final var status = ((JsonString) diffObj.members().get("status")).string();
+ final var status = ((JsonString) diffObj.asMap().get("status")).asString();
if (!"DIFFERENT".equals(status)) continue;
- final var className = ((JsonString) diffObj.members().get("className")).string();
+ final var className = ((JsonString) diffObj.asMap().get("className")).asString();
sb.append("### ").append(className).append("\n\n");
- final var classDiffs = (JsonArray) diffObj.members().get("differences");
+ final var classDiffs = (JsonArray) diffObj.asMap().get("differences");
if (classDiffs != null) {
- for (final var change : classDiffs.elements()) {
+ for (final var change : classDiffs.asList()) {
final var changeObj = (JsonObject) change;
- final var type = ((JsonString) changeObj.members().get("type")).string();
- final var methodValue = changeObj.members().get("method");
- final var method = methodValue instanceof JsonString js ? js.string() : "unknown";
+ final var type = ((JsonString) changeObj.asMap().get("type")).asString();
+ final var methodValue = changeObj.asMap().get("method");
+ final var method = methodValue instanceof JsonString js ? js.asString() : "unknown";
final var emoji = switch (type) {
case "methodRemoved" -> "➖";
@@ -1078,7 +1078,7 @@ static String generateSummary(JsonObject report) {
}
sb.append("---\n");
- final var timestamp = ((JsonString) report.members().get("timestamp")).string();
+ final var timestamp = ((JsonString) report.asMap().get("timestamp")).asString();
sb.append("*Generated by API Tracker on ").append(timestamp.split("T")[0]).append("*\n");
return sb.toString();
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
index d71aec8e..bdb4f947 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
@@ -46,7 +46,7 @@ public static void main(String[] args) {
// Pretty print the report
System.out.println("=== Comparison Report ===");
- final var jsonOutput = Json.toDisplayString(report, 2);
+ final var jsonOutput = Json.toDisplayString(report, " ");
System.out.println(jsonOutput);
// Generate fingerprint and summary
diff --git a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
index 820de79a..3a4501ca 100644
--- a/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
+++ b/json-java21-api-tracker/src/test/java/io/github/simbo1905/tracker/ApiTrackerTest.java
@@ -69,20 +69,20 @@ void testExtractLocalApiJsonObject() {
assertThat(api).isNotNull();
// Check if extraction succeeded or failed
- if (api.members().containsKey("error")) {
+ if (api.asMap().containsKey("error")) {
// If file not found, that's expected for some source setups
- final var error = ((JsonString) api.members().get("error")).string();
+ final var error = ((JsonString) api.asMap().get("error")).asString();
assertThat(error).contains("LOCAL_FILE_NOT_FOUND");
} else {
// If extraction succeeded, validate structure
- assertThat(api.members()).containsKey("className");
- assertThat(((JsonString) api.members().get("className")).string()).isEqualTo("JsonObject");
+ assertThat(api.asMap()).containsKey("className");
+ assertThat(((JsonString) api.asMap().get("className")).asString()).isEqualTo("JsonObject");
- assertThat(api.members()).containsKey("packageName");
- assertThat(((JsonString) api.members().get("packageName")).string()).isEqualTo("jdk.incubator.java.util.json");
+ assertThat(api.asMap()).containsKey("packageName");
+ assertThat(((JsonString) api.asMap().get("packageName")).asString()).isEqualTo("jdk.incubator.java.util.json");
- assertThat(api.members()).containsKey("isInterface");
- assertThat(api.members().get("isInterface")).isEqualTo(JsonBoolean.of(true));
+ assertThat(api.asMap()).containsKey("isInterface");
+ assertThat(api.asMap().get("isInterface")).isEqualTo(JsonBoolean.of(true));
}
}
@@ -92,17 +92,17 @@ void testExtractLocalApiJsonValue() {
final var api = ApiTracker.extractLocalApiFromSource("jdk.incubator.java.util.json.JsonValue");
// Check if extraction succeeded or failed
- if (api.members().containsKey("error")) {
+ if (api.asMap().containsKey("error")) {
// If file not found, that's expected for some source setups
- final var error = ((JsonString) api.members().get("error")).string();
+ final var error = ((JsonString) api.asMap().get("error")).asString();
assertThat(error).contains("LOCAL_FILE_NOT_FOUND");
} else {
// If extraction succeeded, validate structure
- assertThat(api.members()).containsKey("isSealed");
- assertThat(api.members().get("isSealed")).isEqualTo(JsonBoolean.of(true));
+ assertThat(api.asMap()).containsKey("isSealed");
+ assertThat(api.asMap().get("isSealed")).isEqualTo(JsonBoolean.of(true));
- assertThat(api.members()).containsKey("permits");
- final var permits = (JsonArray) api.members().get("permits");
+ assertThat(api.asMap()).containsKey("permits");
+ final var permits = (JsonArray) api.asMap().get("permits");
// May be empty in source parsing if permits aren't explicitly listed
assertThat(permits).isNotNull();
}
@@ -113,8 +113,8 @@ void testExtractLocalApiJsonValue() {
void testExtractLocalApiMissingFile() {
final var api = ApiTracker.extractLocalApiFromSource("jdk.incubator.java.util.json.NonExistentClass");
- assertThat(api.members()).containsKey("error");
- final var error = ((JsonString) api.members().get("error")).string();
+ assertThat(api.asMap()).containsKey("error");
+ final var error = ((JsonString) api.asMap().get("error")).asString();
assertThat(error).contains("LOCAL_FILE_NOT_FOUND");
}
}
@@ -178,9 +178,9 @@ void testCompareApisUpstreamError() {
final var result = ApiTracker.compareApis(local, upstream);
- assertThat(result.members()).containsKey("status");
- assertThat(((JsonString) result.members().get("status")).string()).isEqualTo("UPSTREAM_ERROR");
- assertThat(result.members()).containsKey("error");
+ assertThat(result.asMap()).containsKey("status");
+ assertThat(((JsonString) result.asMap().get("status")).asString()).isEqualTo("UPSTREAM_ERROR");
+ assertThat(result.asMap()).containsKey("error");
}
}
@@ -194,7 +194,7 @@ void testRunFullComparison() {
final var report = ApiTracker.runFullComparison();
assertThat(report).isNotNull();
- assertThat(report.members()).containsKeys(
+ assertThat(report.asMap()).containsKeys(
"timestamp",
"localPackage",
"upstreamPackage",
@@ -203,8 +203,8 @@ void testRunFullComparison() {
"durationMs"
);
- final var summary = (JsonObject) report.members().get("summary");
- assertThat(summary.members()).containsKeys(
+ final var summary = (JsonObject) report.asMap().get("summary");
+ assertThat(summary.asMap()).containsKeys(
"totalClasses",
"matchingClasses",
"missingUpstream",
@@ -212,7 +212,7 @@ void testRunFullComparison() {
);
// Total classes should be greater than 0
- final var totalClasses = summary.members().get("totalClasses");
+ final var totalClasses = summary.asMap().get("totalClasses");
assertThat(totalClasses).isNotNull();
}
}
diff --git a/json-java21-api-tracker/src/test/resources/JsonObject.java b/json-java21-api-tracker/src/test/resources/JsonObject.java
index b8fda77e..1302aa95 100644
--- a/json-java21-api-tracker/src/test/resources/JsonObject.java
+++ b/json-java21-api-tracker/src/test/resources/JsonObject.java
@@ -72,7 +72,7 @@ static JsonObject of(Map map) {
/// {@return {@code true} if the given object is also a {@code JsonObject}
/// and the two {@code JsonObject}s represent the same mappings} Two
/// {@code JsonObject}s {@code jo1} and {@code jo2} represent the same
- /// mappings if {@code jo1.members().equals(jo2.members())}.
+ /// mappings if {@code jo1.asMap().equals(jo2.asMap())}.
///
/// @see #members()
@Override
diff --git a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
index 58b1d831..c58702b6 100644
--- a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
+++ b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java
@@ -139,7 +139,7 @@ private static void evaluatePropertyAccess(
List results) {
if (current instanceof JsonObject obj) {
- final var value = obj.members().get(prop.name());
+ final var value = obj.asMap().get(prop.name());
if (value != null) {
evaluateSegments(segments, index + 1, value, root, results);
}
@@ -155,7 +155,7 @@ private static void evaluateArrayIndex(
List results) {
if (current instanceof JsonArray array) {
- final var elements = array.elements();
+ final var elements = array.asList();
int idx = arr.index();
// Handle negative indices (from end)
@@ -178,7 +178,7 @@ private static void evaluateArraySlice(
List results) {
if (current instanceof JsonArray array) {
- final var elements = array.elements();
+ final var elements = array.asList();
final int size = elements.size();
final int step = slice.step() != null ? slice.step() : 1;
@@ -225,11 +225,11 @@ private static void evaluateWildcard(
List results) {
if (current instanceof JsonObject obj) {
- for (final var value : obj.members().values()) {
+ for (final var value : obj.asMap().values()) {
evaluateSegments(segments, index + 1, value, root, results);
}
} else if (current instanceof JsonArray array) {
- for (final var element : array.elements()) {
+ for (final var element : array.asList()) {
evaluateSegments(segments, index + 1, element, root, results);
}
}
@@ -248,11 +248,11 @@ private static void evaluateRecursiveDescent(
// Then recurse into children
if (current instanceof JsonObject obj) {
- for (final var value : obj.members().values()) {
+ for (final var value : obj.asMap().values()) {
evaluateRecursiveDescent(desc, segments, index, value, root, results);
}
} else if (current instanceof JsonArray array) {
- for (final var element : array.elements()) {
+ for (final var element : array.asList()) {
evaluateRecursiveDescent(desc, segments, index, element, root, results);
}
}
@@ -269,7 +269,7 @@ private static void evaluateTargetSegment(
switch (target) {
case JsonPathAst.PropertyAccess prop -> {
if (current instanceof JsonObject obj) {
- final var value = obj.members().get(prop.name());
+ final var value = obj.asMap().get(prop.name());
if (value != null) {
evaluateSegments(segments, index + 1, value, root, results);
}
@@ -277,18 +277,18 @@ private static void evaluateTargetSegment(
}
case JsonPathAst.Wildcard ignored -> {
if (current instanceof JsonObject obj) {
- for (final var value : obj.members().values()) {
+ for (final var value : obj.asMap().values()) {
evaluateSegments(segments, index + 1, value, root, results);
}
} else if (current instanceof JsonArray array) {
- for (final var element : array.elements()) {
+ for (final var element : array.asList()) {
evaluateSegments(segments, index + 1, element, root, results);
}
}
}
case JsonPathAst.ArrayIndex arr -> {
if (current instanceof JsonArray array) {
- final var elements = array.elements();
+ final var elements = array.asList();
int idx = arr.index();
if (idx < 0) idx = elements.size() + idx;
if (idx >= 0 && idx < elements.size()) {
@@ -310,7 +310,7 @@ private static void evaluateFilter(
List results) {
if (current instanceof JsonArray array) {
- for (final var element : array.elements()) {
+ for (final var element : array.asList()) {
if (matchesFilter(filter.expression(), element)) {
evaluateSegments(segments, index + 1, element, root, results);
}
@@ -359,7 +359,7 @@ private static JsonValue resolvePropertyPath(JsonPathAst.PropertyPath path, Json
JsonValue value = current;
for (final var prop : path.properties()) {
if (value instanceof JsonObject obj) {
- value = obj.members().get(prop);
+ value = obj.asMap().get(prop);
if (value == null) {
return null;
}
@@ -373,9 +373,9 @@ private static JsonValue resolvePropertyPath(JsonPathAst.PropertyPath path, Json
private static Object jsonValueToComparable(JsonValue value) {
if (value == null) return null;
return switch (value) {
- case JsonString s -> s.string();
- case JsonNumber n -> n.toDouble();
- case JsonBoolean b -> b.bool();
+ case JsonString s -> s.asString();
+ case JsonNumber n -> n.asDouble();
+ case JsonBoolean b -> b.asBoolean();
case JsonNull ignored -> null;
default -> value;
};
@@ -470,9 +470,9 @@ private static void evaluateScriptExpression(
// Simple support for @.length-1 pattern
final var scriptText = script.script().trim();
if (scriptText.equals("@.length-1")) {
- final int lastIndex = array.elements().size() - 1;
+ final int lastIndex = array.asList().size() - 1;
if (lastIndex >= 0) {
- evaluateSegments(segments, index + 1, array.elements().get(lastIndex), root, results);
+ evaluateSegments(segments, index + 1, array.asList().get(lastIndex), root, results);
}
} else {
LOG.warning(() -> "Unsupported script expression: " + scriptText);
diff --git a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
index 59221e40..4b528a3f 100644
--- a/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
+++ b/json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPathStreams.java
@@ -34,7 +34,7 @@ public static boolean isNull(JsonValue v) {
/// @throws ClassCastException if the value is not a `JsonNumber`
public static double asDouble(JsonValue v) {
if (v instanceof JsonNumber n) {
- return n.toDouble();
+ return n.asDouble();
}
throw new ClassCastException("Expected JsonNumber but got " + v.getClass().getSimpleName());
}
@@ -42,7 +42,7 @@ public static double asDouble(JsonValue v) {
/// @throws ClassCastException if the value is not a `JsonNumber`
public static long asLong(JsonValue v) {
if (v instanceof JsonNumber n) {
- return n.toLong();
+ return n.asLong();
}
throw new ClassCastException("Expected JsonNumber but got " + v.getClass().getSimpleName());
}
@@ -50,7 +50,7 @@ public static long asLong(JsonValue v) {
/// @throws ClassCastException if the value is not a `JsonString`
public static String asString(JsonValue v) {
if (v instanceof JsonString s) {
- return s.string();
+ return s.asString();
}
throw new ClassCastException("Expected JsonString but got " + v.getClass().getSimpleName());
}
@@ -58,24 +58,24 @@ public static String asString(JsonValue v) {
/// @throws ClassCastException if the value is not a `JsonBoolean`
public static boolean asBoolean(JsonValue v) {
if (v instanceof JsonBoolean b) {
- return b.bool();
+ return b.asBoolean();
}
throw new ClassCastException("Expected JsonBoolean but got " + v.getClass().getSimpleName());
}
public static Double asDoubleOrNull(JsonValue v) {
- return (v instanceof JsonNumber n) ? n.toDouble() : null;
+ return (v instanceof JsonNumber n) ? n.asDouble() : null;
}
public static Long asLongOrNull(JsonValue v) {
- return (v instanceof JsonNumber n) ? n.toLong() : null;
+ return (v instanceof JsonNumber n) ? n.asLong() : null;
}
public static String asStringOrNull(JsonValue v) {
- return (v instanceof JsonString s) ? s.string() : null;
+ return (v instanceof JsonString s) ? s.asString() : null;
}
public static Boolean asBooleanOrNull(JsonValue v) {
- return (v instanceof JsonBoolean b) ? b.bool() : null;
+ return (v instanceof JsonBoolean b) ? b.asBoolean() : null;
}
}
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
index 1b1c4b2c..2ee483f6 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathFilterEvaluationTest.java
@@ -159,7 +159,7 @@ void testComplexNestedLogic() {
// Helper to extract integer field for assertions
private int asInt(JsonValue v, @SuppressWarnings("SameParameterValue") String key) {
if (v instanceof jdk.incubator.java.util.json.JsonObject obj) {
- return (int) obj.members().get(key).toLong();
+ return (int) obj.asMap().get(key).asLong();
}
throw new IllegalArgumentException("Not an object");
}
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
index 0d0645a5..b46ab474 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathGoessnerTest.java
@@ -87,8 +87,8 @@ void testNestedProperty() {
assertThat(results).hasSize(1);
assertThat(results.getFirst()).isInstanceOf(JsonObject.class);
final var bicycle = (JsonObject) results.getFirst();
- assertThat(bicycle.members().get("color")).isInstanceOf(JsonString.class);
- assertThat(bicycle.members().get("color").string()).isEqualTo("red");
+ assertThat(bicycle.asMap().get("color")).isInstanceOf(JsonString.class);
+ assertThat(bicycle.asMap().get("color").asString()).isEqualTo("red");
}
// Goessner Article Examples
@@ -99,7 +99,7 @@ void testAuthorsOfAllBooks() {
final var results = JsonPath.parse("$.store.book[*].author").query(storeJson);
assertThat(results).hasSize(4);
final var authors = results.stream()
- .map(JsonValue::string)
+ .map(JsonValue::asString)
.toList();
assertThat(authors).containsExactly(
"Nigel Rees",
@@ -115,7 +115,7 @@ void testAllBooks() {
final var results = JsonPath.parse("$.store.book").query(storeJson);
assertThat(results).hasSize(1);
assertThat(results.getFirst()).isInstanceOf(JsonArray.class);
- assertThat(((JsonArray) results.getFirst()).elements()).hasSize(4);
+ assertThat(((JsonArray) results.getFirst()).asList()).hasSize(4);
}
@Test
@@ -124,7 +124,7 @@ void testAllAuthorsRecursive() {
final var results = JsonPath.parse("$..author").query(storeJson);
assertThat(results).hasSize(4);
final var authors = results.stream()
- .map(JsonValue::string)
+ .map(JsonValue::asString)
.toList();
assertThat(authors).containsExactlyInAnyOrder(
"Nigel Rees",
@@ -147,7 +147,7 @@ void testAllPricesInStore() {
final var results = JsonPath.parse("$.store..price").query(storeJson);
assertThat(results).hasSize(5); // 4 book prices + 1 bicycle price
final var prices = results.stream()
- .map(JsonValue::toDouble)
+ .map(JsonValue::asDouble)
.toList();
assertThat(prices).containsExactlyInAnyOrder(8.95, 12.99, 8.99, 22.99, 19.95);
}
@@ -158,7 +158,7 @@ void testThirdBook() {
final var results = JsonPath.parse("$..book[2]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("title").string()).isEqualTo("Moby Dick");
+ assertThat(book.asMap().get("title").asString()).isEqualTo("Moby Dick");
}
@Test
@@ -167,7 +167,7 @@ void testLastBookScriptExpression() {
final var results = JsonPath.parse("$..book[(@.length-1)]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("title").string()).isEqualTo("The Lord of the Rings");
+ assertThat(book.asMap().get("title").asString()).isEqualTo("The Lord of the Rings");
}
@Test
@@ -176,7 +176,7 @@ void testLastBookSlice() {
final var results = JsonPath.parse("$..book[-1:]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("title").string()).isEqualTo("The Lord of the Rings");
+ assertThat(book.asMap().get("title").asString()).isEqualTo("The Lord of the Rings");
}
@Test
@@ -185,7 +185,7 @@ void testFirstTwoBooksUnion() {
final var results = JsonPath.parse("$..book[0,1]").query(storeJson);
assertThat(results).hasSize(2);
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactly("Sayings of the Century", "Sword of Honour");
}
@@ -196,7 +196,7 @@ void testFirstTwoBooksSlice() {
final var results = JsonPath.parse("$..book[:2]").query(storeJson);
assertThat(results).hasSize(2);
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactly("Sayings of the Century", "Sword of Honour");
}
@@ -207,7 +207,7 @@ void testBooksWithIsbn() {
final var results = JsonPath.parse("$..book[?(@.isbn)]").query(storeJson);
assertThat(results).hasSize(2);
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactlyInAnyOrder("Moby Dick", "The Lord of the Rings");
}
@@ -218,7 +218,7 @@ void testBooksCheaperThan10() {
final var results = JsonPath.parse("$..book[?(@.price<10)]").query(storeJson);
assertThat(results).hasSize(2);
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactlyInAnyOrder("Sayings of the Century", "Moby Dick");
}
@@ -239,7 +239,7 @@ void testArrayIndexFirst() {
final var results = JsonPath.parse("$.store.book[0]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("author").string()).isEqualTo("Nigel Rees");
+ assertThat(book.asMap().get("author").asString()).isEqualTo("Nigel Rees");
}
@Test
@@ -248,7 +248,7 @@ void testArrayIndexNegative() {
final var results = JsonPath.parse("$.store.book[-1]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("author").string()).isEqualTo("J. R. R. Tolkien");
+ assertThat(book.asMap().get("author").asString()).isEqualTo("J. R. R. Tolkien");
}
@Test
@@ -257,7 +257,7 @@ void testBracketNotationProperty() {
final var results = JsonPath.parse("$['store']['book'][0]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("author").string()).isEqualTo("Nigel Rees");
+ assertThat(book.asMap().get("author").asString()).isEqualTo("Nigel Rees");
}
@Test
@@ -287,7 +287,7 @@ void testSliceWithStep() {
final var results = JsonPath.parse("$.store.book[0:4:2]").query(storeJson);
assertThat(results).hasSize(2); // books at index 0 and 2
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactly("Sayings of the Century", "Moby Dick");
}
@@ -298,7 +298,7 @@ void testSliceReverse() {
final var results = JsonPath.parse("$.store.book[::-1]").query(storeJson);
assertThat(results).hasSize(4);
final var titles = results.stream()
- .map(v -> v.members().get("title").string())
+ .map(v -> v.asMap().get("title").asString())
.toList();
assertThat(titles).containsExactly(
"The Lord of the Rings",
@@ -313,7 +313,7 @@ void testDeepNestedAccess() {
LOG.info(() -> "TEST: testDeepNestedAccess - $.store.book[0].title");
final var results = JsonPath.parse("$.store.book[0].title").query(storeJson);
assertThat(results).hasSize(1);
- assertThat(results.getFirst().string()).isEqualTo("Sayings of the Century");
+ assertThat(results.getFirst().asString()).isEqualTo("Sayings of the Century");
}
@Test
@@ -337,7 +337,7 @@ void testFilterGreaterThan() {
final var results = JsonPath.parse("$..book[?(@.price>20)]").query(storeJson);
assertThat(results).hasSize(1);
final var book = (JsonObject) results.getFirst();
- assertThat(book.members().get("title").string()).isEqualTo("The Lord of the Rings");
+ assertThat(book.asMap().get("title").asString()).isEqualTo("The Lord of the Rings");
}
@Test
@@ -373,7 +373,7 @@ void testFilterLogicalAnd() {
LOG.info(() -> "TEST: testFilterLogicalAnd - $.store.book[?(@.isbn && @.price>20)]");
final var results = JsonPath.parse("$.store.book[?(@.isbn && @.price>20)]").query(storeJson);
assertThat(results).hasSize(1);
- assertThat(((JsonObject) results.getFirst()).members().get("title").string()).isEqualTo("The Lord of the Rings");
+ assertThat(((JsonObject) results.getFirst()).asMap().get("title").asString()).isEqualTo("The Lord of the Rings");
}
// Fluent API tests
@@ -385,7 +385,7 @@ void testFluentApiParseAndSelect() {
assertThat(matches).hasSize(1);
assertThat(matches.getFirst()).isInstanceOf(JsonArray.class);
final var bookArray = (JsonArray) matches.getFirst();
- assertThat(bookArray.elements()).hasSize(4); // 4 books in the array
+ assertThat(bookArray.asList()).hasSize(4); // 4 books in the array
}
@Test
@@ -394,7 +394,7 @@ void testStaticQueryWithCompiledPath() {
final var compiled = JsonPath.parse("$.store.book[*].author");
final var results = JsonPath.query(compiled, storeJson);
assertThat(results).hasSize(4);
- assertThat(results.stream().map(JsonValue::string).toList()).containsExactly(
+ assertThat(results.stream().map(JsonValue::asString).toList()).containsExactly(
"Nigel Rees",
"Evelyn Waugh",
"Herman Melville",
@@ -417,7 +417,7 @@ void testFluentApiReusable() {
""");
final var simpleResults = compiledPath.query(simpleDoc);
assertThat(simpleResults).hasSize(1);
- assertThat(simpleResults.getFirst().toDouble()).isEqualTo(99.99);
+ assertThat(simpleResults.getFirst().asDouble()).isEqualTo(99.99);
}
@Test
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
index f49de7b8..1e546c9f 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
@@ -26,7 +26,7 @@ static void emit(CodeBuilder cob, JtdSchema.DiscriminatorSchema d,
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
@@ -52,7 +52,7 @@ static void emit(CodeBuilder cob, JtdSchema.DiscriminatorSchema d,
cob.aload(tagValSlot);
cob.checkcast(CD_JsonString);
- cob.invokeinterface(CD_JsonString, "string", MTD_String);
+ cob.invokeinterface(CD_JsonString, "asString", MTD_String);
int tagStrSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(tagStrSlot);
@@ -113,7 +113,7 @@ static void emitDynamic(CodeBuilder cob, JtdSchema.DiscriminatorSchema d,
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
@@ -137,7 +137,7 @@ static void emitDynamic(CodeBuilder cob, JtdSchema.DiscriminatorSchema d,
cob.aload(tagValSlot);
cob.checkcast(CD_JsonString);
- cob.invokeinterface(CD_JsonString, "string", MTD_String);
+ cob.invokeinterface(CD_JsonString, "asString", MTD_String);
int tagStrSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(tagStrSlot);
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitElements.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitElements.java
index 9ded40cd..e3b33f2c 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitElements.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitElements.java
@@ -65,7 +65,7 @@ private static void emitLoop(CodeBuilder cob, JtdSchema.ElementsSchema e,
String prefix, String childSchemaPath) {
cob.aload(instSlot);
cob.checkcast(CD_JsonArray);
- cob.invokeinterface(CD_JsonArray, "elements", MTD_List);
+ cob.invokeinterface(CD_JsonArray, "asList", MTD_List);
int listSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(listSlot);
@@ -111,7 +111,7 @@ private static void emitLoopDynamic(CodeBuilder cob, JtdSchema.ElementsSchema e,
int prefixSlot, String childSchemaPath) {
cob.aload(instSlot);
cob.checkcast(CD_JsonArray);
- cob.invokeinterface(CD_JsonArray, "elements", MTD_List);
+ cob.invokeinterface(CD_JsonArray, "asList", MTD_List);
int listSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(listSlot);
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitEnum.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitEnum.java
index 9125a513..b572cecc 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitEnum.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitEnum.java
@@ -57,7 +57,7 @@ private static void emitEnumCore(CodeBuilder cob, JtdSchema.EnumSchema e,
cob.aload(instSlot);
cob.checkcast(CD_JsonString);
- cob.invokeinterface(CD_JsonString, "string", MTD_String);
+ cob.invokeinterface(CD_JsonString, "asString", MTD_String);
int strSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(strSlot);
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitProperties.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitProperties.java
index 44e8e05b..bb3ba957 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitProperties.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitProperties.java
@@ -30,7 +30,7 @@ static void emit(CodeBuilder cob, JtdSchema.PropertiesSchema p,
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
@@ -85,7 +85,7 @@ static void emitDynamic(CodeBuilder cob, JtdSchema.PropertiesSchema p,
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
index a430428e..26c95929 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
@@ -239,7 +239,7 @@ private static void emitIntCore(CodeBuilder cob, String type,
cob.aload(instSlot);
cob.checkcast(CD_JsonNumber);
- cob.invokeinterface(CD_JsonNumber, "toDouble", MTD_double);
+ cob.invokeinterface(CD_JsonNumber, "asDouble", MTD_double);
int dSlot = cob.allocateLocal(TypeKind.DOUBLE);
cob.dstore(dSlot);
@@ -251,7 +251,7 @@ private static void emitIntCore(CodeBuilder cob, String type,
cob.aload(instSlot);
cob.checkcast(CD_JsonNumber);
- cob.invokeinterface(CD_JsonNumber, "toLong", MTD_long);
+ cob.invokeinterface(CD_JsonNumber, "asLong", MTD_long);
int lSlot = cob.allocateLocal(TypeKind.LONG);
cob.lstore(lSlot);
@@ -283,7 +283,7 @@ private static void emitTimestampCore(CodeBuilder cob, int instSlot, int errSlot
cob.aload(instSlot);
cob.checkcast(CD_JsonString);
- cob.invokeinterface(CD_JsonString, "string", MTD_String);
+ cob.invokeinterface(CD_JsonString, "asString", MTD_String);
int strSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(strSlot);
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitValues.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitValues.java
index 78837976..e6bf86bc 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitValues.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitValues.java
@@ -61,7 +61,7 @@ private static void emitLoop(CodeBuilder cob, JtdSchema.ValuesSchema v,
String prefix, String childSchemaPath) {
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
@@ -73,7 +73,7 @@ private static void emitLoopDynamic(CodeBuilder cob, JtdSchema.ValuesSchema v,
int prefixSlot, String childSchemaPath) {
cob.aload(instSlot);
cob.checkcast(CD_JsonObject);
- cob.invokeinterface(CD_JsonObject, "members", MTD_Map);
+ cob.invokeinterface(CD_JsonObject, "asMap", MTD_Map);
int mapSlot = cob.allocateLocal(TypeKind.REFERENCE);
cob.astore(mapSlot);
diff --git a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
index f5079efc..265d01b0 100644
--- a/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
+++ b/json-java21-jtd-codegen/src/test/java/json/java21/jtd/codegen/CodegenSpecConformanceTest.java
@@ -32,7 +32,7 @@ static Stream cases() throws IOException {
assert root instanceof JsonObject : "expected top-level object";
final var obj = (JsonObject) root;
- return obj.members().entrySet().stream()
+ return obj.asMap().entrySet().stream()
.map(entry -> Arguments.of(
entry.getKey(),
entry.getValue()));
@@ -50,18 +50,18 @@ void codegenMatchesSpecSuite(String name, JsonValue caseValue) {
}
final var caseObj = (JsonObject) caseValue;
- final var schema = caseObj.members().get("schema");
- final var instance = caseObj.members().get("instance");
- final var expectedErrors = (JsonArray) caseObj.members().get("errors");
+ final var schema = caseObj.asMap().get("schema");
+ final var instance = caseObj.asMap().get("instance");
+ final var expectedErrors = (JsonArray) caseObj.asMap().get("errors");
final var codegen = JtdCodegen.compile(schema);
final var result = codegen.validate(instance);
- final var expected = expectedErrors.elements().stream()
+ final var expected = expectedErrors.asList().stream()
.map(e -> {
final var errObj = (JsonObject) e;
- final var ip = toJsonPointer((JsonArray) errObj.members().get("instancePath"));
- final var sp = toJsonPointer((JsonArray) errObj.members().get("schemaPath"));
+ final var ip = toJsonPointer((JsonArray) errObj.asMap().get("instancePath"));
+ final var sp = toJsonPointer((JsonArray) errObj.asMap().get("schemaPath"));
return new JtdValidationError(ip, sp);
})
.sorted(ERR_CMP)
@@ -77,11 +77,11 @@ void codegenMatchesSpecSuite(String name, JsonValue caseValue) {
}
private static String toJsonPointer(JsonArray tokens) {
- if (tokens.elements().isEmpty()) return "";
+ if (tokens.asList().isEmpty()) return "";
final var sb = new StringBuilder();
- for (final var token : tokens.elements()) {
+ for (final var token : tokens.asList()) {
sb.append('/');
- sb.append(((JsonString) token).string());
+ sb.append(((JsonString) token).asString());
}
return sb.toString();
}
diff --git a/json-java21-jtd/JTD_CODEGEN_SPEC.md b/json-java21-jtd/JTD_CODEGEN_SPEC.md
index f4d98e74..c82677e6 100644
--- a/json-java21-jtd/JTD_CODEGEN_SPEC.md
+++ b/json-java21-jtd/JTD_CODEGEN_SPEC.md
@@ -267,7 +267,7 @@ syntax-based.
Target-language expression examples:
- JavaScript (uint8): `typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 255`
-- Java (uint8): `v instanceof JsonNumber n && n.toDouble() == Math.floor(n.toDouble()) && n.toLong() >= 0 && n.toLong() <= 255`
+- Java (uint8): `v instanceof JsonNumber n && n.asDouble() == Math.floor(n.asDouble()) && n.asLong() >= 0 && n.asLong() <= 255`
## 5. Emission Rules
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java b/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
index 760cfc5e..8060bbcf 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/InterpreterValidator.java
@@ -98,7 +98,7 @@ private void stepType(Frame frame, JtdSchema.TypeSchema type, List errors) {
if (!(frame.instance() instanceof jdk.incubator.java.util.json.JsonString str)
- || !enumS.values().contains(str.string())) {
+ || !enumS.values().contains(str.asString())) {
errors.add(new JtdValidationError(frame.ptr(), frame.schemaPath() + "/enum"));
}
}
@@ -111,7 +111,7 @@ private void stepElements(Frame frame, JtdSchema.ElementsSchema elems,
}
final var childSchemaPath = frame.schemaPath() + "/elements";
int i = 0;
- for (final var element : arr.elements()) {
+ for (final var element : arr.asList()) {
stack.push(new Frame(elems.elements(), element,
frame.ptr() + "/" + i,
frame.crumbs().withArrayIndex(i),
@@ -128,7 +128,7 @@ private void stepProperties(Frame frame, JtdSchema.PropertiesSchema props,
return;
}
- final var members = obj.members();
+ final var members = obj.asMap();
final var discKey = frame.discriminatorKey();
final var sp = frame.schemaPath();
@@ -181,7 +181,7 @@ private void stepValues(Frame frame, JtdSchema.ValuesSchema vals,
return;
}
final var childSchemaPath = frame.schemaPath() + "/values";
- for (final var entry : obj.members().entrySet()) {
+ for (final var entry : obj.asMap().entrySet()) {
stack.push(new Frame(vals.values(), entry.getValue(),
frame.ptr() + "/" + entry.getKey(),
frame.crumbs().withObjectField(entry.getKey()),
@@ -196,7 +196,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
return;
}
- final var members = obj.members();
+ final var members = obj.asMap();
final var sp = frame.schemaPath();
if (!members.containsKey(disc.discriminator())) {
@@ -212,7 +212,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
return;
}
- final var variant = disc.mapping().get(tagStr.string());
+ final var variant = disc.mapping().get(tagStr.asString());
if (variant == null) {
errors.add(new JtdValidationError(
frame.ptr() + "/" + disc.discriminator(),
@@ -222,7 +222,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
stack.push(new Frame(variant, frame.instance(), frame.ptr(),
frame.crumbs(),
- sp + "/mapping/" + tagStr.string(),
+ sp + "/mapping/" + tagStr.asString(),
disc.discriminator()));
}
@@ -232,7 +232,7 @@ private void stepDiscriminator(Frame frame, JtdSchema.DiscriminatorSchema disc,
private static boolean isTimestamp(JsonValue instance) {
if (!(instance instanceof jdk.incubator.java.util.json.JsonString str)) return false;
- final var value = str.string();
+ final var value = str.asString();
if (!JtdSchema.TypeSchema.RFC3339.matcher(value).matches()) return false;
try {
final var normalized = value.replace(":60", ":59");
@@ -245,10 +245,10 @@ private static boolean isTimestamp(JsonValue instance) {
private static boolean isIntInRange(JsonValue instance, long min, long max) {
if (!(instance instanceof jdk.incubator.java.util.json.JsonNumber num)) return false;
- final var d = num.toDouble();
+ final var d = num.asDouble();
if (d != Math.floor(d)) return false;
if (d > Long.MAX_VALUE || d < Long.MIN_VALUE) return false;
- final var l = num.toLong();
+ final var l = num.asLong();
return l >= min && l <= max;
}
}
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java b/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
index 03e217e6..47e3c059 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java
@@ -142,7 +142,7 @@ void validatePropertiesSchema(Frame frame, JtdSchema.PropertiesSchema propsSchem
// Check for missing required properties
for (var entry : propsSchema.properties().entrySet()) {
String key = entry.getKey();
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
if (value == null) {
// Missing required property - create error with containing object offset
@@ -157,13 +157,13 @@ void validatePropertiesSchema(Frame frame, JtdSchema.PropertiesSchema propsSchem
// RFC 8927 §2.2.8: Only the discriminator field is exempt from additionalProperties enforcement
if (!propsSchema.additionalProperties()) {
String discriminatorKey = frame.discriminatorKey();
- for (String key : obj.members().keySet()) {
+ for (String key : obj.asMap().keySet()) {
if (!propsSchema.properties().containsKey(key) && !propsSchema.optionalProperties().containsKey(key)) {
// Only exempt the discriminator field itself, not all additional properties
if (key.equals(discriminatorKey)) {
continue; // Skip the discriminator field - it's exempt
}
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
// Additional property not allowed - create error with the value's offset
String error = Jtd.Error.ADDITIONAL_PROPERTY_NOT_ALLOWED.message(key);
String enrichedError = Jtd.enrichedError(error, frame, value);
@@ -185,7 +185,7 @@ void pushChildFrames(Frame frame, java.util.Deque stack) {
case JtdSchema.ElementsSchema elementsSchema -> {
if (instance instanceof JsonArray arr) {
int index = 0;
- for (JsonValue element : arr.elements()) {
+ for (JsonValue element : arr.asList()) {
String childPtr = frame.ptr() + "/" + index;
Crumbs childCrumbs = frame.crumbs().withArrayIndex(index);
Frame childFrame = new Frame(elementsSchema.elements(), element, childPtr, childCrumbs);
@@ -208,7 +208,7 @@ void pushChildFrames(Frame frame, java.util.Deque stack) {
continue;
}
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
if (value != null) {
String childPtr = frame.ptr() + "/" + key;
@@ -229,7 +229,7 @@ void pushChildFrames(Frame frame, java.util.Deque stack) {
}
JtdSchema childSchema = entry.getValue();
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
if (value != null) {
String childPtr = frame.ptr() + "/" + key;
@@ -244,7 +244,7 @@ void pushChildFrames(Frame frame, java.util.Deque stack) {
}
case JtdSchema.ValuesSchema valuesSchema -> {
if (instance instanceof JsonObject obj) {
- for (var entry : obj.members().entrySet()) {
+ for (var entry : obj.asMap().entrySet()) {
String key = entry.getKey();
JsonValue value = entry.getValue();
String childPtr = frame.ptr() + "/" + key;
@@ -257,9 +257,9 @@ void pushChildFrames(Frame frame, java.util.Deque stack) {
}
case JtdSchema.DiscriminatorSchema discSchema -> {
if (instance instanceof JsonObject obj) {
- JsonValue discriminatorValue = obj.members().get(discSchema.discriminator());
+ JsonValue discriminatorValue = obj.asMap().get(discSchema.discriminator());
if (discriminatorValue instanceof JsonString discStr) {
- String discriminatorValueStr = discStr.string();
+ String discriminatorValueStr = discStr.asString();
JtdSchema variantSchema = discSchema.mapping().get(discriminatorValueStr);
if (variantSchema != null) {
@@ -302,31 +302,31 @@ JtdSchema compileSchema(JsonValue schema, boolean isRoot) {
}
// RFC 8927: Only root schemas can contain definitions
- if (!isRoot && obj.members().containsKey("definitions")) {
+ if (!isRoot && obj.asMap().containsKey("definitions")) {
throw new IllegalArgumentException("Nested schemas cannot contain definitions, found: " +
- Json.toDisplayString(obj, 0));
+ Json.toDisplayString(obj, ""));
}
// First pass: register definition keys as placeholders (only for root schemas)
- if (isRoot && obj.members().containsKey("definitions")) {
- JsonValue definitionsValue = obj.members().get("definitions");
+ if (isRoot && obj.asMap().containsKey("definitions")) {
+ JsonValue definitionsValue = obj.asMap().get("definitions");
if (!(definitionsValue instanceof JsonObject defsObj)) {
throw new IllegalArgumentException("definitions must be an object");
}
- for (String key : defsObj.members().keySet()) {
+ for (String key : defsObj.asMap().keySet()) {
definitions.putIfAbsent(key, null);
}
}
// Second pass: compile each definition if not already compiled (only for root schemas)
- if (isRoot && obj.members().containsKey("definitions")) {
- JsonValue definitionsValue = obj.members().get("definitions");
+ if (isRoot && obj.asMap().containsKey("definitions")) {
+ JsonValue definitionsValue = obj.asMap().get("definitions");
if (!(definitionsValue instanceof JsonObject defsObj)) {
throw new IllegalArgumentException("definitions must be an object");
}
- for (String key : defsObj.members().keySet()) {
+ for (String key : defsObj.asMap().keySet()) {
if (definitions.get(key) == null) {
- JsonValue rawDef = defsObj.members().get(key);
+ JsonValue rawDef = defsObj.asMap().get(key);
// Compile definitions normally (RFC 8927 strict)
JtdSchema compiled = compileSchema(rawDef, false); // Definitions are not root schemas
definitions.put(key, compiled);
@@ -343,7 +343,7 @@ JtdSchema compileSchema(JsonValue schema, boolean isRoot) {
JtdSchema compileObjectSchema(JsonObject obj) {
// Check for mutually-exclusive schema forms
List forms = new ArrayList<>();
- Map members = obj.members();
+ Map members = obj.asMap();
if (members.containsKey("ref")) forms.add("ref");
if (members.containsKey("type")) forms.add("type");
@@ -401,14 +401,14 @@ JtdSchema compileObjectSchema(JsonObject obj) {
}
if (members.containsKey("mapping") && !members.containsKey("discriminator")) {
throw new IllegalArgumentException("mapping can only appear with discriminator in schema: " +
- Json.toDisplayString(obj, 0));
+ Json.toDisplayString(obj, ""));
}
// Parse the specific schema form
JtdSchema schema;
// RFC 8927: {} is the empty form and accepts all instances
- if (forms.isEmpty() && obj.members().isEmpty()) {
+ if (forms.isEmpty() && obj.asMap().isEmpty()) {
LOG.finer(() -> "Empty schema {} encountered. Per RFC 8927 this means 'accept anything'. "
+ "Some non-JTD validators interpret {} with object semantics; this implementation follows RFC 8927.");
return new JtdSchema.EmptySchema();
@@ -419,10 +419,10 @@ JtdSchema compileObjectSchema(JsonObject obj) {
JsonValue nullableValue = members.get("nullable");
if (!(nullableValue instanceof JsonBoolean bool)) {
throw new IllegalArgumentException("nullable must be a boolean, found: " +
- nullableValue.getClass().getSimpleName() + " in schema: " + Json.toDisplayString(obj, 0));
+ nullableValue.getClass().getSimpleName() + " in schema: " + Json.toDisplayString(obj, ""));
}
// If nullable is valid, this becomes a nullable empty schema
- if (bool.bool()) {
+ if (bool.asBoolean()) {
return new JtdSchema.NullableSchema(new JtdSchema.EmptySchema());
}
}
@@ -463,9 +463,9 @@ JtdSchema compileObjectSchema(JsonObject obj) {
JsonValue nullableValue = members.get("nullable");
if (!(nullableValue instanceof JsonBoolean bool)) {
throw new IllegalArgumentException("nullable must be a boolean, found: " +
- nullableValue.getClass().getSimpleName() + " in schema: " + Json.toDisplayString(obj, 0));
+ nullableValue.getClass().getSimpleName() + " in schema: " + Json.toDisplayString(obj, ""));
}
- if (bool.bool()) {
+ if (bool.asBoolean()) {
return new JtdSchema.NullableSchema(schema);
}
}
@@ -474,29 +474,29 @@ JtdSchema compileObjectSchema(JsonObject obj) {
}
JtdSchema compileRefSchema(JsonObject obj) {
- JsonValue refValue = obj.members().get("ref");
+ JsonValue refValue = obj.asMap().get("ref");
if (!(refValue instanceof JsonString str)) {
throw new IllegalArgumentException("ref must be a string");
}
- String ref = str.string();
+ String ref = str.asString();
// RFC 8927: Validate that ref points to an existing definition at compile time
if (!definitions.containsKey(ref)) {
throw new IllegalArgumentException("ref '" + ref + "' points to non-existent definition in schema: " +
- Json.toDisplayString(obj, 0));
+ Json.toDisplayString(obj, ""));
}
return new JtdSchema.RefSchema(ref, definitions);
}
JtdSchema compileTypeSchema(JsonObject obj) {
- Map members = obj.members();
+ Map members = obj.asMap();
// Validate that only expected keys are present
for (String key : members.keySet()) {
if (!key.equals("type") && !key.equals("nullable") && !key.equals("metadata") && !key.equals("definitions")) {
throw new IllegalArgumentException("Type schema contains unknown key: '" + key +
- "' in schema: " + Json.toDisplayString(obj, 0));
+ "' in schema: " + Json.toDisplayString(obj, ""));
}
}
@@ -505,7 +505,7 @@ JtdSchema compileTypeSchema(JsonObject obj) {
throw new IllegalArgumentException("type must be a string");
}
- String typeStr = str.string();
+ String typeStr = str.asString();
// RFC 8927 §2.2.3: Validate that type is one of the supported primitive types
if (!VALID_TYPES.contains(typeStr)) {
@@ -517,18 +517,18 @@ JtdSchema compileTypeSchema(JsonObject obj) {
}
JtdSchema compileEnumSchema(JsonObject obj) {
- Map members = obj.members();
+ Map members = obj.asMap();
JsonValue enumValue = members.get("enum");
if (!(enumValue instanceof JsonArray arr)) {
throw new IllegalArgumentException("enum must be an array");
}
List values = new ArrayList<>();
- for (JsonValue value : arr.elements()) {
+ for (JsonValue value : arr.asList()) {
if (!(value instanceof JsonString str)) {
throw new IllegalArgumentException("enum values must be strings");
}
- values.add(str.string());
+ values.add(str.asString());
}
if (values.isEmpty()) {
@@ -546,7 +546,7 @@ JtdSchema compileEnumSchema(JsonObject obj) {
}
JtdSchema compileElementsSchema(JsonObject obj) {
- Map members = obj.members();
+ Map members = obj.asMap();
JsonValue elementsValue = members.get("elements");
JtdSchema elementsSchema = compileSchema(elementsValue, false); // Elements are nested schemas
return new JtdSchema.ElementsSchema(elementsSchema);
@@ -556,7 +556,7 @@ JtdSchema compilePropertiesSchema(JsonObject obj) {
Map properties = Map.of();
Map optionalProperties = Map.of();
- Map members = obj.members();
+ Map members = obj.asMap();
// Parse required properties
if (members.containsKey("properties")) {
@@ -581,7 +581,7 @@ JtdSchema compilePropertiesSchema(JsonObject obj) {
if (optionalProperties.containsKey(key)) {
throw new IllegalArgumentException("Key '" + key +
"' cannot be defined in both properties and optionalProperties in schema: " +
- Json.toDisplayString(obj, 0));
+ Json.toDisplayString(obj, ""));
}
}
@@ -592,26 +592,26 @@ JtdSchema compilePropertiesSchema(JsonObject obj) {
if (!(addPropsValue instanceof JsonBoolean bool)) {
throw new IllegalArgumentException("additionalProperties must be a boolean");
}
- additionalProperties = bool.bool();
+ additionalProperties = bool.asBoolean();
} // Empty schema with no properties defined rejects additional properties by default
return new JtdSchema.PropertiesSchema(properties, optionalProperties, additionalProperties);
}
JtdSchema compileValuesSchema(JsonObject obj) {
- Map members = obj.members();
+ Map members = obj.asMap();
JsonValue valuesValue = members.get("values");
JtdSchema valuesSchema = compileSchema(valuesValue, false); // Values are nested schemas
return new JtdSchema.ValuesSchema(valuesSchema);
}
JtdSchema compileDiscriminatorSchema(JsonObject obj) {
- Map members = obj.members();
+ Map members = obj.asMap();
JsonValue discriminatorValue = members.get("discriminator");
if (!(discriminatorValue instanceof JsonString discStr)) {
throw new IllegalArgumentException("discriminator must be a string");
}
- String discriminatorKey = discStr.string();
+ String discriminatorKey = discStr.asString();
JsonValue mappingValue = members.get("mapping");
if (!(mappingValue instanceof JsonObject mappingObj)) {
@@ -619,8 +619,8 @@ JtdSchema compileDiscriminatorSchema(JsonObject obj) {
}
Map mapping = new java.util.HashMap<>();
- for (String key : mappingObj.members().keySet()) {
- JsonValue variantValue = mappingObj.members().get(key);
+ for (String key : mappingObj.asMap().keySet()) {
+ JsonValue variantValue = mappingObj.asMap().get(key);
// Early validation: mapping values must be objects (for PropertiesSchema)
if (!(variantValue instanceof JsonObject)) {
@@ -630,9 +630,9 @@ JtdSchema compileDiscriminatorSchema(JsonObject obj) {
JsonObject variantObj = (JsonObject) variantValue;
// Check for nullable flag before compiling
- if (variantObj.members().containsKey("nullable") &&
- variantObj.members().get("nullable") instanceof JsonBoolean bool &&
- bool.bool()) {
+ if (variantObj.asMap().containsKey("nullable") &&
+ variantObj.asMap().get("nullable") instanceof JsonBoolean bool &&
+ bool.asBoolean()) {
throw new IllegalArgumentException("Discriminator mapping '" + key + "' cannot be nullable");
}
@@ -693,8 +693,8 @@ void validateDiscriminatorMapping(String mappingKey, JtdSchema variantSchema, St
/// Extracts and stores top-level definitions for ref resolution
private Map parsePropertySchemas(JsonObject propsObj) {
Map schemas = new java.util.HashMap<>();
- for (String key : propsObj.members().keySet()) {
- JsonValue schemaValue = propsObj.members().get(key);
+ for (String key : propsObj.asMap().keySet()) {
+ JsonValue schemaValue = propsObj.asMap().get(key);
schemas.put(key, compileSchema(schemaValue, false));
}
return schemas;
@@ -782,7 +782,7 @@ public String message(Object... args) {
/// Creates a verbose error message including the actual JSON value
public String message(JsonValue invalidValue, Object... args) {
String baseMessage = String.format(messageTemplate, args);
- String displayValue = Json.toDisplayString(invalidValue, 0); // Use compact format
+ String displayValue = Json.toDisplayString(invalidValue, ""); // Use compact format
return baseMessage + " (was: " + displayValue + ")";
}
}
diff --git a/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java b/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
index 1963d3ac..0c146faa 100644
--- a/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
+++ b/json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
@@ -148,7 +148,7 @@ boolean validateStringWithFrame(Frame frame, java.util.List errors, bool
boolean validateTimestampWithFrame(Frame frame, java.util.List errors, boolean verboseErrors) {
JsonValue instance = frame.instance();
if (instance instanceof JsonString str) {
- String value = str.string();
+ String value = str.asString();
if (RFC3339.matcher(value).matches()) {
try {
// Replace :60 with :59 to allow leap seconds through parsing
@@ -169,7 +169,7 @@ boolean validateIntegerWithFrame(Frame frame, String type, java.util.List longValue >= -128 && longValue <= 127;
case "uint8" -> longValue >= 0 && longValue <= 255;
@@ -237,12 +237,12 @@ record EnumSchema(List values) implements JtdSchema {
public boolean validateWithFrame(Frame frame, java.util.List errors, boolean verboseErrors) {
JsonValue instance = frame.instance();
if (instance instanceof JsonString str) {
- if (values.contains(str.string())) {
+ if (values.contains(str.asString())) {
return true;
}
String error = verboseErrors
- ? Jtd.Error.VALUE_NOT_IN_ENUM.message(instance, str.string(), values)
- : Jtd.Error.VALUE_NOT_IN_ENUM.message(str.string(), values);
+ ? Jtd.Error.VALUE_NOT_IN_ENUM.message(instance, str.asString(), values)
+ : Jtd.Error.VALUE_NOT_IN_ENUM.message(str.asString(), values);
errors.add(Jtd.enrichedError(error, frame, instance));
return false;
}
@@ -268,7 +268,7 @@ public Jtd.Result validate(JsonValue instance) {
@Override
public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
if (instance instanceof JsonArray arr) {
- for (JsonValue element : arr.elements()) {
+ for (JsonValue element : arr.asList()) {
Jtd.Result result = elements.validate(element, verboseErrors);
if (!result.isValid()) {
return result;
@@ -331,7 +331,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
String key = entry.getKey();
JtdSchema schema = entry.getValue();
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
if (value == null) {
return Jtd.Result.failure(Jtd.Error.MISSING_REQUIRED_PROPERTY.message(key));
}
@@ -347,7 +347,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
String key = entry.getKey();
JtdSchema schema = entry.getValue();
- JsonValue value = obj.members().get(key);
+ JsonValue value = obj.asMap().get(key);
if (value != null) {
Jtd.Result result = schema.validate(value, verboseErrors);
if (!result.isValid()) {
@@ -358,7 +358,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
// Check for additional properties if not allowed
if (!additionalProperties) {
- for (String key : obj.members().keySet()) {
+ for (String key : obj.asMap().keySet()) {
if (!properties.containsKey(key) && !optionalProperties.containsKey(key)) {
return Jtd.Result.failure(Jtd.Error.ADDITIONAL_PROPERTY_NOT_ALLOWED.message(key));
}
@@ -409,7 +409,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
return Jtd.Result.failure(error);
}
- for (JsonValue value : obj.members().values()) {
+ for (JsonValue value : obj.asMap().values()) {
Jtd.Result result = values.validate(value, verboseErrors);
if (!result.isValid()) {
return result;
@@ -462,7 +462,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
return Jtd.Result.failure(error);
}
- JsonValue discriminatorValue = obj.members().get(discriminator);
+ JsonValue discriminatorValue = obj.asMap().get(discriminator);
if (!(discriminatorValue instanceof JsonString discStr)) {
String error = verboseErrors
? Jtd.Error.DISCRIMINATOR_MUST_BE_STRING.message(discriminatorValue, discriminator)
@@ -470,7 +470,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
return Jtd.Result.failure(error);
}
- String discriminatorValueStr = discStr.string();
+ String discriminatorValueStr = discStr.asString();
JtdSchema variantSchema = mapping.get(discriminatorValueStr);
if (variantSchema == null) {
String error = verboseErrors
@@ -482,7 +482,7 @@ public Jtd.Result validate(JsonValue instance, boolean verboseErrors) {
// Special-case: allow objects with only the discriminator key
// This handles the case where discriminator maps to simple types like "boolean"
// and the object contains only the discriminator field
- if (obj.members().size() == 1 && obj.members().containsKey(discriminator)) {
+ if (obj.asMap().size() == 1 && obj.asMap().containsKey(discriminator)) {
return Jtd.Result.success();
}
@@ -503,7 +503,7 @@ public boolean validateWithFrame(Frame frame, java.util.List errors, boo
return false;
}
- JsonValue discriminatorValue = obj.members().get(discriminator);
+ JsonValue discriminatorValue = obj.asMap().get(discriminator);
if (!(discriminatorValue instanceof JsonString discStr)) {
String error = verboseErrors
? Jtd.Error.DISCRIMINATOR_MUST_BE_STRING.message(discriminatorValue, discriminator)
@@ -513,7 +513,7 @@ public boolean validateWithFrame(Frame frame, java.util.List errors, boo
return false;
}
- String discriminatorValueStr = discStr.string();
+ String discriminatorValueStr = discStr.asString();
JtdSchema variantSchema = mapping.get(discriminatorValueStr);
if (variantSchema == null) {
String error = verboseErrors
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
index 708a2d15..6e9dd190 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdPropertyTest.java
@@ -108,10 +108,10 @@ private static List createFailingJtdDocuments(JtdTestSchema schema, J
case TypeSchema(var type) -> createFailingTypeValues(type);
case EnumSchema(var ignored) -> List.of(JsonString.of("invalid-enum-value"));
case ElementsSchema(var elementSchema) -> {
- if (compliant instanceof JsonArray arr && !arr.elements().isEmpty()) {
- final var invalidElement = createFailingJtdDocuments(elementSchema, arr.elements().getFirst());
+ if (compliant instanceof JsonArray arr && !arr.asList().isEmpty()) {
+ final var invalidElement = createFailingJtdDocuments(elementSchema, arr.asList().getFirst());
if (!invalidElement.isEmpty()) {
- final var mixedArray = JsonArray.of(List.of(arr.elements().getFirst(), invalidElement.getFirst()));
+ final var mixedArray = JsonArray.of(List.of(arr.asList().getFirst(), invalidElement.getFirst()));
yield List.of(mixedArray, JsonNull.of());
}
}
@@ -158,20 +158,20 @@ private static List createFailingTypeValues(String type) {
}
private static JsonObject removeProperty(JsonObject original, String missingProperty) {
- final var filtered = original.members().entrySet().stream().filter(entry -> !Objects.equals(entry.getKey(), missingProperty)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (left, right) -> left, LinkedHashMap::new));
+ final var filtered = original.asMap().entrySet().stream().filter(entry -> !Objects.equals(entry.getKey(), missingProperty)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (left, right) -> left, LinkedHashMap::new));
return JsonObject.of(filtered);
}
@SuppressWarnings("SameParameterValue")
private static JsonObject addExtraProperty(JsonObject original, String extraProperty) {
- final var extended = new LinkedHashMap<>(original.members());
+ final var extended = new LinkedHashMap<>(original.asMap());
extended.put(extraProperty, JsonString.of("extra-value"));
return JsonObject.of(extended);
}
@SuppressWarnings("SameParameterValue")
private static JsonValue replaceDiscriminatorValue(JsonObject original, String newValue) {
- final var modified = new LinkedHashMap<>(original.members());
+ final var modified = new LinkedHashMap<>(original.asMap());
// Find and replace discriminator field
for (var entry : modified.entrySet()) {
if (entry.getValue() instanceof JsonString) {
@@ -212,7 +212,7 @@ case DiscriminatorSchema(var discriminator, var mapping) -> {
}
case NullableSchema(var inner) -> {
final var innerSchema = jtdSchemaToJsonObject(inner);
- final var nullableMap = new LinkedHashMap<>(innerSchema.members());
+ final var nullableMap = new LinkedHashMap<>(innerSchema.asMap());
nullableMap.put("nullable", JsonBoolean.of(true));
yield JsonObject.of(nullableMap);
}
@@ -416,8 +416,8 @@ void exhaustiveJtdValidation(@ForAll("jtdSchemas") JtdPropertyTest.JtdTestSchema
if (!validationResult.isValid()) {
String errorMessage = String.format(
"ERROR: Compliant document failed validation!%nSchema JSON: %s%nDocument JSON: %s%nValidation Errors: %s%nSchema Description: %s%nFull Schema Object: %s",
- Json.toDisplayString(schemaJson, 2),
- Json.toDisplayString(compliantDocument, 2),
+ Json.toDisplayString(schemaJson, " "),
+ Json.toDisplayString(compliantDocument, " "),
validationResult.errors(),
schemaDescription,
schema
@@ -445,8 +445,8 @@ void exhaustiveJtdValidation(@ForAll("jtdSchemas") JtdPropertyTest.JtdTestSchema
if (failingResult.isValid()) {
LOG.severe(() -> String.format("UNEXPECTED: Failing document passed validation!%nSchema JSON: %s%nDocument JSON: %s%nExpected: FAILURE, Got: SUCCESS",
- Json.toDisplayString(schemaJson, 2),
- Json.toDisplayString(failing, 2)));
+ Json.toDisplayString(schemaJson, " "),
+ Json.toDisplayString(failing, " ")));
}
assertThat(failingResult.isValid()).as("Expected JTD validation failure for %s against schema %s", failing, schemaDescription).isFalse();
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
index 722c3cf6..a207b039 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
@@ -32,7 +32,7 @@ static Stream cases() throws IOException {
assert root instanceof JsonObject : "expected top-level object";
final var obj = (JsonObject) root;
- return obj.members().entrySet().stream()
+ return obj.asMap().entrySet().stream()
.map(entry -> Arguments.of(
entry.getKey(),
entry.getValue()));
@@ -45,18 +45,18 @@ void interpreterMatchesSpecSuite(String name, JsonValue caseValue) {
LOG.info("SPEC: " + name);
final var caseObj = (JsonObject) caseValue;
- final var schema = caseObj.members().get("schema");
- final var instance = caseObj.members().get("instance");
- final var expectedErrors = (JsonArray) caseObj.members().get("errors");
+ final var schema = caseObj.asMap().get("schema");
+ final var instance = caseObj.asMap().get("instance");
+ final var expectedErrors = (JsonArray) caseObj.asMap().get("errors");
final var validator = JtdValidator.compileInterpreter(schema);
final var result = validator.validate(instance);
- final var expected = expectedErrors.elements().stream()
+ final var expected = expectedErrors.asList().stream()
.map(e -> {
final var errObj = (JsonObject) e;
- final var ip = toJsonPointer((JsonArray) errObj.members().get("instancePath"));
- final var sp = toJsonPointer((JsonArray) errObj.members().get("schemaPath"));
+ final var ip = toJsonPointer((JsonArray) errObj.asMap().get("instancePath"));
+ final var sp = toJsonPointer((JsonArray) errObj.asMap().get("schemaPath"));
return new JtdValidationError(ip, sp);
})
.sorted(ERR_CMP)
@@ -72,11 +72,11 @@ void interpreterMatchesSpecSuite(String name, JsonValue caseValue) {
}
private static String toJsonPointer(JsonArray tokens) {
- if (tokens.elements().isEmpty()) return "";
+ if (tokens.asList().isEmpty()) return "";
final var sb = new StringBuilder();
- for (final var token : tokens.elements()) {
+ for (final var token : tokens.asList()) {
sb.append('/');
- sb.append(((JsonString) token).string());
+ sb.append(((JsonString) token).asString());
}
return sb.toString();
}
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
index e582db3d..55efc2fa 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
@@ -900,8 +900,8 @@ public void testInt8RangeValidationWithDoubleValues() {
Jtd.Result result = validator.validate(schema, outOfRange);
LOG.fine(() -> "Testing int8 range with Double value: " + outOfRange +
- " (JsonNumber.toLong(): " +
- outOfRange.toLong() + ")");
+ " (JsonNumber.asLong(): " +
+ outOfRange.asLong() + ")");
// This should fail but currently passes due to the bug
assertThat(result.isValid())
@@ -1043,13 +1043,13 @@ public void testJsonNumberToNumberReturnsDouble() {
// Verify that JsonNumber works properly for typical JSON numbers
assertThat(numberValue).isInstanceOf(JsonNumber.class);
- long longValue = numberValue.toLong();
+ long longValue = numberValue.asLong();
- LOG.info(() -> "JsonNumber.toLong() returns: " + longValue +
+ LOG.info(() -> "JsonNumber.asLong() returns: " + longValue +
" for value: " + numberValue);
- // This demonstrates what value JsonNumber.toLong() returns for typical values
- LOG.info(() -> "JsonNumber.toLong() returns: " + longValue +
+ // This demonstrates what value JsonNumber.asLong() returns for typical values
+ LOG.info(() -> "JsonNumber.asLong() returns: " + longValue +
" for value: " + numberValue);
// The key test is that regardless of the Number type, range validation should work
@@ -1069,7 +1069,7 @@ public void testIntegerValidationExplicitDouble() {
Jtd.Result result = validator.validate(schema, doubleValue);
LOG.fine(() -> "Explicit Double validation - value: " + doubleValue +
- ", toDouble(): " + doubleValue.toDouble());
+ ", toDouble(): " + doubleValue.asDouble());
// This should fail (1000 is way outside int8 range of -128 to 127)
assertThat(result.isValid())
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java b/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
index ac340b92..5f21de7d 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/EscapedKeyBugTest.java
@@ -51,8 +51,8 @@ public void testEscapedCharactersInKeys() {
JsonObject obj = (JsonObject) result;
// Verify both keys are parsed correctly
- assertEquals(1L, ((JsonNumber) obj.members().get("foo\nbar")).toLong());
- assertEquals(2L, ((JsonNumber) obj.members().get("foo\tbar")).toLong());
+ assertEquals(1L, ((JsonNumber) obj.asMap().get("foo\nbar")).asLong());
+ assertEquals(2L, ((JsonNumber) obj.asMap().get("foo\tbar")).asLong());
}
@Test
@@ -67,7 +67,7 @@ public void testEscapedQuoteInKey() {
JsonObject obj = (JsonObject) result;
// Verify key with escaped quote is parsed correctly
- assertEquals(1L, ((JsonNumber) obj.members().get("foo\"bar")).toLong());
+ assertEquals(1L, ((JsonNumber) obj.asMap().get("foo\"bar")).asLong());
}
@Test
@@ -82,7 +82,7 @@ public void testEscapedBackslashInKey() {
JsonObject obj = (JsonObject) result;
// Verify key with escaped backslash is parsed correctly
- assertEquals(1L, ((JsonNumber) obj.members().get("foo\\bar")).toLong());
+ assertEquals(1L, ((JsonNumber) obj.asMap().get("foo\\bar")).asLong());
}
@Test
@@ -97,6 +97,6 @@ public void testMultipleEscapedCharactersInKey() {
JsonObject obj = (JsonObject) result;
// Verify key with multiple escaped characters is parsed correctly
- assertEquals(1L, ((JsonNumber) obj.members().get("foo\n\t\"bar")).toLong());
+ assertEquals(1L, ((JsonNumber) obj.asMap().get("foo\n\t\"bar")).asLong());
}
}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java b/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
index e8ef76ce..d27e3e7b 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/ReadmeDemoTests.java
@@ -18,8 +18,8 @@ void quickStartExample() {
assertThat(value).isInstanceOf(JsonObject.class);
JsonObject obj = (JsonObject) value;
- assertThat(((JsonString) obj.members().get("name")).string()).isEqualTo("Alice");
- assertThat(((JsonNumber) obj.members().get("age")).toLong()).isEqualTo(30L);
+ assertThat(((JsonString) obj.asMap().get("name")).asString()).isEqualTo("Alice");
+ assertThat(((JsonNumber) obj.asMap().get("age")).asLong()).isEqualTo(30L);
String roundTrip = value.toString();
assertThat(roundTrip).isEqualTo(jsonString);
@@ -55,22 +55,22 @@ void recordMappingExample() {
// Verify the JSON structure
assertThat(teamJson).isInstanceOf(JsonObject.class);
JsonObject teamObj = (JsonObject) teamJson;
- assertThat(((JsonString) teamObj.members().get("teamName")).string()).isEqualTo("Engineering");
+ assertThat(((JsonString) teamObj.asMap().get("teamName")).asString()).isEqualTo("Engineering");
- JsonArray members = (JsonArray) teamObj.members().get("members");
- assertThat(members.elements()).hasSize(2);
+ JsonArray members = (JsonArray) teamObj.asMap().get("members");
+ assertThat(members.asList()).hasSize(2);
// Parse JSON back to records
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
Team reconstructed = new Team(
- ((JsonString) parsed.members().get("teamName")).string(),
- ((JsonArray) parsed.members().get("members")).elements().stream()
+ ((JsonString) parsed.asMap().get("teamName")).asString(),
+ ((JsonArray) parsed.asMap().get("members")).asList().stream()
.map(v -> {
JsonObject member = (JsonObject) v;
return new User(
- ((JsonString) member.members().get("name")).string(),
- ((JsonString) member.members().get("email")).string(),
- ((JsonBoolean) member.members().get("active")).bool()
+ ((JsonString) member.asMap().get("name")).asString(),
+ ((JsonString) member.asMap().get("email")).asString(),
+ ((JsonBoolean) member.asMap().get("active")).asBoolean()
);
})
.toList()
@@ -106,19 +106,19 @@ void builderPatternExample() {
));
// Verify structure
- assertThat(((JsonString) response.members().get("status")).string()).isEqualTo("success");
+ assertThat(((JsonString) response.asMap().get("status")).asString()).isEqualTo("success");
- JsonObject data = (JsonObject) response.members().get("data");
- JsonObject user = (JsonObject) data.members().get("user");
- assertThat(((JsonNumber) user.members().get("id")).toLong()).isEqualTo(12345L);
- assertThat(((JsonString) user.members().get("name")).string()).isEqualTo("John Doe");
+ JsonObject data = (JsonObject) response.asMap().get("data");
+ JsonObject user = (JsonObject) data.asMap().get("user");
+ assertThat(((JsonNumber) user.asMap().get("id")).asLong()).isEqualTo(12345L);
+ assertThat(((JsonString) user.asMap().get("name")).asString()).isEqualTo("John Doe");
- JsonArray roles = (JsonArray) user.members().get("roles");
- assertThat(roles.elements()).hasSize(2);
- assertThat(((JsonString) roles.elements().getFirst()).string()).isEqualTo("admin");
+ JsonArray roles = (JsonArray) user.asMap().get("roles");
+ assertThat(roles.asList()).hasSize(2);
+ assertThat(((JsonString) roles.asList().getFirst()).asString()).isEqualTo("admin");
- JsonArray errors = (JsonArray) response.members().get("errors");
- assertThat(errors.elements()).isEmpty();
+ JsonArray errors = (JsonArray) response.asMap().get("errors");
+ assertThat(errors.asList()).isEmpty();
}
@Test
@@ -136,10 +136,10 @@ void streamingProcessingExample() {
// Process a large array of records
JsonArray items = (JsonArray) Json.parse(largeJsonArray);
- List activeUserEmails = items.elements().stream()
+ List activeUserEmails = items.asList().stream()
.map(v -> (JsonObject) v)
- .filter(obj -> ((JsonBoolean) obj.members().get("active")).bool())
- .map(obj -> ((JsonString) obj.members().get("email")).string())
+ .filter(obj -> ((JsonBoolean) obj.asMap().get("active")).asBoolean())
+ .map(obj -> ((JsonString) obj.asMap().get("email")).asString())
.toList();
// Verify we got only active users
@@ -177,7 +177,7 @@ void displayFormattingExample() {
));
// Format for display
- String formatted = Json.toDisplayString(data, 2);
+ String formatted = Json.toDisplayString(data, " ");
// Verify it contains proper formatting (checking key parts)
assertThat(formatted).contains("{\n");
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
index f224ab48..18209404 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
@@ -14,7 +14,7 @@ void ofDoubleToStringPreservesValue() {
@Test
void ofDoubleToDoubleWorks() {
var jn = JsonNumber.of(123.45);
- assertThat(jn.toDouble()).isEqualTo(123.45);
+ assertThat(jn.asDouble()).isEqualTo(123.45);
}
@Test
@@ -22,13 +22,13 @@ void ofDoubleThenToLongForIntegralDouble() {
// 123.0 should be convertible to long 123
var jn = JsonNumber.of(123.0);
System.out.println("toString: " + jn.toString());
- assertThat(jn.toLong()).isEqualTo(123L);
+ assertThat(jn.asLong()).isEqualTo(123L);
}
@Test
void ofDoubleThenToLongForNonIntegralShouldThrow() {
var jn = JsonNumber.of(123.45);
- assertThatThrownBy(() -> jn.toLong())
- .isInstanceOf(jdk.incubator.java.util.json.JsonAssertionException.class);
+ assertThatThrownBy(() -> jn.asLong())
+ .isInstanceOf(jdk.incubator.java.util.json.JsonValueException.class);
}
}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
index 96147547..21f17593 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamples.java
@@ -43,8 +43,8 @@ static void quickStartExample() {
System.out.println("Value type: " + value.getClass().getSimpleName());
JsonObject obj = (JsonObject) value;
- String name = ((JsonString) obj.members().get("name")).string();
- long age = ((JsonNumber) obj.members().get("age")).toLong();
+ String name = ((JsonString) obj.asMap().get("name")).asString();
+ long age = ((JsonNumber) obj.asMap().get("age")).asLong();
System.out.println("Extracted name: " + name);
System.out.println("Extracted age: " + age);
@@ -82,14 +82,14 @@ static void recordMappingExample() {
// Parse JSON back to records
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
Team reconstructed = new Team(
- ((JsonString) parsed.members().get("teamName")).string(),
- ((JsonArray) parsed.members().get("members")).elements().stream()
+ ((JsonString) parsed.asMap().get("teamName")).asString(),
+ ((JsonArray) parsed.asMap().get("members")).asList().stream()
.map(v -> {
JsonObject member = (JsonObject) v;
return new User(
- ((JsonString) member.members().get("name")).string(),
- ((JsonString) member.members().get("email")).string(),
- ((JsonBoolean) member.members().get("active")).bool()
+ ((JsonString) member.asMap().get("name")).asString(),
+ ((JsonString) member.asMap().get("email")).asString(),
+ ((JsonBoolean) member.asMap().get("active")).asBoolean()
);
})
.toList()
@@ -122,7 +122,7 @@ static void builderPatternExample() {
));
System.out.println("API Response:");
- System.out.println(Json.toDisplayString(response, 2));
+ System.out.println(Json.toDisplayString(response, " "));
System.out.println();
}
@@ -143,10 +143,10 @@ static void streamingProcessingExample() {
// Process a large array of records
JsonArray items = (JsonArray) Json.parse(largeJsonArray);
- List activeUserEmails = items.elements().stream()
+ List activeUserEmails = items.asList().stream()
.map(v -> (JsonObject) v)
- .filter(obj -> ((JsonBoolean) obj.members().get("active")).bool())
- .map(obj -> ((JsonString) obj.members().get("email")).string())
+ .filter(obj -> ((JsonBoolean) obj.asMap().get("active")).asBoolean())
+ .map(obj -> ((JsonString) obj.asMap().get("email")).asString())
.toList();
System.out.println("Active user emails: " + activeUserEmails);
@@ -193,7 +193,7 @@ static void displayFormattingExample() {
));
// Format for display
- String formatted = Json.toDisplayString(data, 2);
+ String formatted = Json.toDisplayString(data, " ");
System.out.println("Formatted JSON:");
System.out.println(formatted);
System.out.println();
From ef91166a53b98a76e7107dca1eedebadfdd4d74f Mon Sep 17 00:00:00 2001
From: Simon Massey <322608+simbo1905@users.noreply.github.com>
Date: Sun, 30 Aug 2026 07:54:13 +0100
Subject: [PATCH 7/9] Issue #145 port upstream tests, verify #118 closed, bump
frontier docs
- Port all nine upstream jtreg/junit test files from jdk-sandbox json
branch at frontier 43325738c into json-java21 (TestAccess, TestGenerate,
TestJsonArray, TestJsonLiteral, TestJsonNumber, TestJsonObject,
TestJsonString, TestOtherImpl, TestParse): mechanical jtreg header
removal, package rename, FieldSource to MethodSource (JUnit 5.10),
boxed type patterns for the Java 21 backport of factoryTest.
- Add JsonTestLoggingConfig base class (JUL per repo test rules) and
JsonNumberOfDoubleMatrixTest proving issue #118 is closed by upstream:
of(double) now computes decimal/exponent offsets via indexOf and the
reworked JsonNumberImpl handles integral doubles, fractions, negatives,
zero variants and out-of-range conversions identically to the historic
of(String) delegation fix.
- Add junit-jupiter-params test dependency to json-java21.
- README: bump synced frontier anchor c1a4f80 to 43325738c (2026-08-27),
mark the incubator migration DONE (issue #145), refresh stale accessor
lists (asString/asLong/asInt/asBoolean/asList/asMap, tryGet/tryValue),
fix JsonParseException getErrorLine/getErrorPosition example, record
the #118 closed-by-upstream disposition in Upstream Bug Fixes.
- ci.yml: exp_tests 1355 -> 1665 (full clean verify, all modules green).
---
.github/workflows/ci.yml | 2 +-
README.md | 38 +-
json-java21/pom.xml | 5 +
.../json/JsonNumberOfDoubleMatrixTest.java | 116 +++++
.../java/util/json/JsonTestLoggingConfig.java | 57 +++
.../incubator/java/util/json/TestAccess.java | 198 ++++++++
.../java/util/json/TestGenerate.java | 228 +++++++++
.../java/util/json/TestJsonArray.java | 144 ++++++
.../java/util/json/TestJsonLiteral.java | 107 +++++
.../java/util/json/TestJsonNumber.java | 435 ++++++++++++++++++
.../java/util/json/TestJsonObject.java | 405 ++++++++++++++++
.../java/util/json/TestJsonString.java | 180 ++++++++
.../java/util/json/TestOtherImpl.java | 121 +++++
.../incubator/java/util/json/TestParse.java | 238 ++++++++++
14 files changed, 2255 insertions(+), 19 deletions(-)
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberOfDoubleMatrixTest.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/JsonTestLoggingConfig.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestAccess.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestGenerate.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonArray.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumber.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonObject.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonString.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestOtherImpl.java
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/TestParse.java
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 25ef45bc..de0213b9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
- exp_tests=1355
+ exp_tests=1665
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}")
diff --git a/README.md b/README.md
index 83ab0096..0d6d989b 100644
--- a/README.md
+++ b/README.md
@@ -123,14 +123,16 @@ double ageDouble = obj.get("age").asDouble(); // Returns 30.0
```
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 (for JsonArray)
-- `members()` - Returns Map (for JsonObject)
+- `asString()` - Returns the String value (for JsonString)
+- `asLong()` - Returns the long value (for JsonNumber, if representable)
+- `asDouble()` - Returns the double value (for JsonNumber, if representable)
+- `asBoolean()` - Returns the boolean value (for JsonBoolean)
+- `asList()` - Returns List (for JsonArray)
+- `asMap()` - Returns Map (for JsonObject)
- `get(String name)` - Access JsonObject member by name
-- `element(int index)` - Access JsonArray element by index
+- `get(int index)` - Access JsonArray element by index
+- `tryGet(String name)` - Returns Optional for a JsonObject member
+- `tryValue()` - Returns Optional, empty for JsonNull
### Realistic Record Mapping
@@ -222,9 +224,9 @@ try {
JsonValue value = Json.parse(userInput);
// Process valid JSON
} catch (JsonParseException e) {
- // Handle malformed JSON with line/column information
- System.err.println("Invalid JSON at line " + e.getLine() +
- ", column " + e.getColumn() + ": " + e.getMessage());
+ // Handle malformed JSON with line/position information
+ System.err.println("Invalid JSON at line " + e.getErrorLine() +
+ ", position " + e.getErrorPosition() + ": " + e.getMessage());
}
```
@@ -289,17 +291,17 @@ The test data is bundled as ZIP files and extracted automatically at runtime:
**Final `java.util.json` sandbox-era release** (2026-05-19).
-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`.
+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.
### API Summary
-- `JsonValue` conversion methods: `asBoolean()`, `toInt()`, `toLong()`, `toDouble()`, `asString()`
-- `JsonValue` navigation methods: `get(String)`, `get(int)`, `getOrAbsent(String)`, `valueOrNull()`
-- `JsonArray`: `elements()`, `of(List)`
-- `JsonObject`: `members()`, `of(Map)`
+- `JsonValue` conversion methods: `asBoolean()`, `asString()`, `asInt()`, `asLong()`, `asDouble()`
+- `JsonValue` navigation methods: `get(String)`, `get(int)`, `tryGet(String)`, `tryValue()`
+- `JsonArray`: `asList()`, `of(List)`
+- `JsonObject`: `asMap()`, `of(Map)`
- `Json`: `parse(String)`, `parse(char[])`, `toDisplayString(JsonValue, String indent)`
### Upstream Migration Notice
-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.
+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`.
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)
@@ -327,9 +329,9 @@ This is a simplified backport with the following changes from the original:
### Upstream Bug Fixes
-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:
+Historically this backport carried local fixes against the upstream OpenJDK jdk-sandbox code. With the uplift to upstream frontier `43325738c` their disposition is:
-- **`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()`.
+- **`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.
## Security Considerations
diff --git a/json-java21/pom.xml b/json-java21/pom.xml
index bf53cf35..8d5dcf55 100644
--- a/json-java21/pom.xml
+++ b/json-java21/pom.xml
@@ -34,6 +34,11 @@
junit-jupiter-engine
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
org.assertj
assertj-core
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberOfDoubleMatrixTest.java b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberOfDoubleMatrixTest.java
new file mode 100644
index 00000000..97b3a728
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberOfDoubleMatrixTest.java
@@ -0,0 +1,116 @@
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/// Evidence matrix for issue #118 (`JsonNumber.of(double)` decimal/exponent
+/// offsets). The historic local fix delegated `of(double)` to `of(String)`;
+/// upstream has since reworked the numeric logic to compute the offsets via
+/// `indexOf`. These tests prove the uplifted upstream implementation handles
+/// the full matrix that motivated #118, making the carried fix unnecessary.
+public class JsonNumberOfDoubleMatrixTest extends JsonTestLoggingConfig {
+
+ private static void assertIntegralDouble(double d, long expected) {
+ var jn = JsonNumber.of(d);
+ // of(double) must be equivalent to the historic of(String) delegation
+ var viaString = JsonNumber.of(Double.toString(d));
+ assertEquals(viaString.toString(), jn.toString(), "toString for " + d);
+ assertEquals(expected, jn.asLong(), "asLong for " + d);
+ assertEquals((int) expected, jn.asInt(), "asInt for " + d);
+ assertEquals(d, jn.asDouble(), "asDouble for " + d);
+ }
+
+ @Test
+ void integralDoublesConvertExactly() {
+ assertIntegralDouble(123.0, 123L);
+ assertIntegralDouble(1.0E2, 100L);
+ assertIntegralDouble(42.0, 42L);
+ assertIntegralDouble(420e-1, 42L);
+ assertIntegralDouble(42e6, 42_000_000L);
+ assertIntegralDouble(0.0, 0L);
+ assertIntegralDouble(1.0, 1L);
+ assertIntegralDouble(5.000, 5L);
+ }
+
+ @Test
+ void negativeIntegralDoublesConvertExactly() {
+ assertIntegralDouble(-123.0, -123L);
+ assertIntegralDouble(-42e6, -42_000_000L);
+ assertIntegralDouble(-1.0, -1L);
+ }
+
+ @Test
+ void negativeZeroBehaves() {
+ var jn = JsonNumber.of(-0.0);
+ assertEquals("-0.0", jn.toString());
+ assertEquals(0L, jn.asLong());
+ assertEquals(0, jn.asInt());
+ assertEquals(-0.0, jn.asDouble());
+ assertEquals(0.0, jn.asDouble() + 0.0);
+ }
+
+ @Test
+ void fractionalDoublesAreNotIntegral() {
+ assertNotIntegral(123.45);
+ assertNotIntegral(0.1);
+ assertNotIntegral(0.002);
+ assertNotIntegral(-123.45);
+ }
+
+ private static void assertNotIntegral(double d) {
+ var jn = JsonNumber.of(d);
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for " + d);
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for " + d);
+ assertEquals(d, jn.asDouble(), "asDouble for " + d);
+ // of(double) must match the of(String) representation exactly
+ assertEquals(JsonNumber.of(Double.toString(d)).toString(), jn.toString());
+ }
+
+ @Test
+ void outOfRangeIntegralThrowsJsonValueException() {
+ assertNotConvertibleToLong(1e300);
+ assertNotConvertibleToLong(-1e300);
+ assertNotConvertibleToLong(Double.MAX_VALUE);
+ assertNotConvertibleToLong(9.3e18);
+ }
+
+ private static void assertNotConvertibleToLong(double d) {
+ var jn = JsonNumber.of(d);
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for " + d);
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for " + d);
+ assertEquals(d, jn.asDouble(), "asDouble for " + d);
+ }
+
+ @Test
+ void tinyMagnitudesRoundTripAsDouble() {
+ var tiny = JsonNumber.of(4.9E-324);
+ assertEquals(4.9E-324, tiny.asDouble());
+ assertThrows(JsonValueException.class, tiny::asLong);
+ var small = JsonNumber.of(5e-100);
+ assertEquals(5e-100, small.asDouble());
+ assertThrows(JsonValueException.class, small::asLong);
+ }
+
+ @Test
+ void ofDoubleMatchesParsedEquivalent() {
+ // of(double) goes through Double.toString; parsing that same text must
+ // produce the same conversions (the equivalence #118 was about)
+ for (double d : new double[]{123.0, 1.0E2, 42e6, 0.0, -0.0, 123.45, 1e300, 4.9E-324}) {
+ var factory = JsonNumber.of(d);
+ var parsed = Json.parse(Double.toString(d));
+ assertNotEquals(parsed, factory); // identity semantics; compare behaviour
+ assertEquals(factory.toString(), parsed.toString(), "toString for " + d);
+ assertEquals(factory.asDouble(), ((JsonNumber) parsed).asDouble(), "asDouble for " + d);
+ }
+ }
+
+ @Test
+ void nonFiniteDoublesRejected() {
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NaN));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.POSITIVE_INFINITY));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NEGATIVE_INFINITY));
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/JsonTestLoggingConfig.java b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonTestLoggingConfig.java
new file mode 100644
index 00000000..679d1e60
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonTestLoggingConfig.java
@@ -0,0 +1,57 @@
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/// Base class for the ported upstream JSON tests. Configures JUL logging from
+/// the `java.util.logging.ConsoleHandler.level` system property so that
+/// `-Djava.util.logging.ConsoleHandler.level=FINE` (etc.) works uniformly, and
+/// announces each test method execution at INFO level.
+public class JsonTestLoggingConfig {
+
+ static final Logger LOG = Logger.getLogger(JsonTestLoggingConfig.class.getName());
+
+ @BeforeAll
+ static void enableJulDebug() {
+ Logger root = Logger.getLogger("");
+ String levelProp = System.getProperty("java.util.logging.ConsoleHandler.level");
+ Level targetLevel = resolveLevel(levelProp);
+ // Ensure the root logger honors the most verbose configured level
+ if (root.getLevel() == null || root.getLevel().intValue() > targetLevel.intValue()) {
+ root.setLevel(targetLevel);
+ }
+ for (var handler : root.getHandlers()) {
+ Level handlerLevel = handler.getLevel();
+ if (handlerLevel == null || handlerLevel.intValue() > targetLevel.intValue()) {
+ handler.setLevel(targetLevel);
+ }
+ }
+ LOG.config(() -> "JUL level configured for JSON tests: " + targetLevel);
+ }
+
+ private static Level resolveLevel(String levelProp) {
+ Level targetLevel = Level.INFO;
+ if (levelProp != null) {
+ try {
+ targetLevel = Level.parse(levelProp.trim());
+ } catch (IllegalArgumentException ex) {
+ try {
+ targetLevel = Level.parse(levelProp.trim().toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException ignored) {
+ LOG.warning(() -> "Unrecognized logging level from 'java.util.logging.ConsoleHandler.level': " + levelProp);
+ }
+ }
+ }
+ return targetLevel;
+ }
+
+ @BeforeEach
+ void announceTest(TestInfo testInfo) {
+ LOG.info(() -> "Running test: " + testInfo.getDisplayName());
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestAccess.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestAccess.java
new file mode 100644
index 00000000..1c6f58df
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestAccess.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonValueException;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TestAccess extends JsonTestLoggingConfig {
+
+ private static final JsonValue JSON_ROOT_ARRAY = Json.parse(
+ """
+ [
+ { "name": "John",
+ "age": 42
+ },
+ {
+ "name": "Mary",
+ "age": 31
+ }
+ ]
+ """);
+
+ private static final JsonValue JSON_ROOT_OBJECT = Json.parse(
+ """
+ {
+ "id" : 1,
+ "values" : [ "value", null ], "valuesWithCommas" : [ "v[a]lu}\\"e", "va,,,[lue,", "value,,", 15],
+ "foo" : { "bar" : "baz" },
+ "qux" : [ [true], { "in" : { } } ],
+ "ba\\"zz" : ["Key with escape"],
+ "obj" : { "1" : "{", "z" : 2}
+ }
+ """);
+
+ private static final JsonValue JSON_NESTED_ARRAY =
+ // Don't use the API we are testing (get(String))
+ JSON_ROOT_OBJECT.asMap().get("values");
+
+ @Test
+ void basicAccessTest() {
+ JSON_ROOT_OBJECT.get("id");
+ assertEquals("value", JSON_ROOT_OBJECT.get("values").get(0).asString());
+ assertTrue(JSON_ROOT_OBJECT.get("values").get(1) instanceof JsonNull);
+ assertTrue(JSON_ROOT_OBJECT.get("qux").get(0).get(0).asBoolean());
+ }
+
+ @Test
+ void boolAndNullFailureTest() {
+ var json = Json.parse("{ \"foo\" : null, \"bar\" : false, \"baz\" : true }");
+ assertThrows(JsonValueException.class, () -> json.get("foo").tryGet("_"));
+ assertThrows(JsonValueException.class, () -> json.get("bar").tryGet("_"));
+ assertThrows(JsonValueException.class, () -> json.get("baz").tryGet("_"));
+ }
+
+ @Test
+ void basicAccessAbsenceTest() {
+ var json = Json.parse("{ \"foo\" : null, \"bar\" : \"words\" }");
+ assertEquals(Optional.empty(), json.tryGet("baz"));
+ assertNull(json.tryGet("baz").map(JsonValue::asString).orElse(null));
+ assertEquals("words", json.tryGet("bar").map(JsonValue::asString).orElse(null));
+ assertThrows(JsonValueException.class, () -> json.get("foo").tryGet("baz"));
+ }
+
+ @Test
+ void basicAccessNullTest() {
+ var json = Json.parse("{ \"foo\" : null, \"bar\" : \"words\" }");
+ assertEquals(Optional.empty(), json.get("foo").tryValue());
+ assertNull(json.get("foo").tryValue().map(JsonValue::asString).orElse(null));
+ assertEquals("words", json.get("bar").tryValue().map(JsonValue::asString).orElse(null));
+ }
+
+ // Ensure that syntactical chars w/in JsonString do not affect path building
+ @Test
+ void stringTest() {
+ assertEquals("JsonNumber is not a JsonString. Path: \"{valuesWithCommas[3\". Location: line 2, position 96.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_OBJECT.get("valuesWithCommas").get(3).asString()).getMessage());
+ assertEquals("JsonNumber is not a JsonBoolean. Path: \"{obj{z\". Location: line 6, position 31.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_OBJECT.get("obj").get("z").asBoolean()).getMessage());
+ }
+
+ @Test
+ void leafExceptionTest() {
+ assertEquals("JsonNumber is not a JsonString. Path: \"[1{age\". Location: line 6, position 11.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_ARRAY.get(1).get("age").asString()).getMessage());
+ }
+
+ @Test
+ void rootArrayTest() {
+ assertEquals("JsonObject member \"asge\" does not exist. Path: \"[1\". Location: line 4, position 2.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_ARRAY.get(1).get("asge").asLong()).getMessage());
+ }
+
+ // Ensure member name with escapes works
+ @Test
+ void escapedKeyTest() {
+ assertEquals("JsonArray index 1 out of bounds for length 1. Path: \"{ba\\\"zz\". Location: line 5, position 15.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_OBJECT.get("ba\"zz").get(1)).getMessage());
+ }
+
+ @Test
+ void multiNestedTest() {
+ assertEquals("JsonObject member \"zap\" does not exist. Path: \"{qux[1{in\". Location: line 4, position 31.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_OBJECT.get("qux").get(1).get("in").get("zap")).getMessage());
+ }
+
+ // Check array path building behavior for first element, expects '['.
+ @Test
+ void firstArrayElementTest() {
+ assertEquals("JsonArray index 5 out of bounds for length 1. Path: \"{qux[0\". Location: line 4, position 14.",
+ assertThrows(JsonValueException.class,
+ () -> JSON_ROOT_OBJECT.get("qux").get(0).get(5)).getMessage());
+ }
+
+ // Operations on JsonObject
+ @Test
+ void failObjectAccessTest() {
+ // Points to the start of the root object -> { ...
+ assertEquals("JsonObject is not a JsonArray. Path: \"\". Location: line 0, position 3.",
+ assertThrows(JsonValueException.class, () -> JSON_ROOT_OBJECT.get(0)).getMessage());
+ assertEquals("JsonObject member \"car\" does not exist. Path: \"\". Location: line 0, position 3.",
+ assertThrows(JsonValueException.class, () -> JSON_ROOT_OBJECT.get("car")).getMessage());
+ }
+
+ // Operations on JsonArray
+ @Test
+ void failArrayAccessTest() {
+ // Points to the JsonArray value of "values"; starts at -> [ "value", null ] ...
+ assertEquals("JsonArray is not a JsonObject. Path: \"{values\". Location: line 2, position 15.",
+ assertThrows(JsonValueException.class, () -> JSON_NESTED_ARRAY.get("foo")).getMessage());
+ assertEquals("JsonArray index 3 out of bounds for length 2. Path: \"{values\". Location: line 2, position 15.",
+ assertThrows(JsonValueException.class, () -> JSON_NESTED_ARRAY.get(3)).getMessage());
+ }
+
+ @Test
+ void failNPETest() {
+ // NPE at JsonObject
+ assertThrows(NullPointerException.class, () -> JSON_ROOT_OBJECT.get(null));
+ // NPE at JsonValue
+ assertThrows(NullPointerException.class, () -> JSON_NESTED_ARRAY.get(null));
+ }
+
+ // Access on factory created JSON number/string should not have a path
+ @Test
+ void factoryPathTest() {
+ assertFalse(assertThrows(JsonValueException.class, () -> JsonArray.of(
+ List.of(JsonNumber.of(1.5))).get(0).asLong()).getMessage().contains("Path"));
+ assertFalse(assertThrows(JsonValueException.class, () -> JsonArray.of(
+ List.of(JsonNumber.of(1))).get(0).asBoolean()).getMessage().contains("Path"));
+ assertFalse(assertThrows(JsonValueException.class, () -> JsonArray.of(
+ List.of(JsonNumber.of(1L))).get(0).asBoolean()).getMessage().contains("Path"));
+ assertFalse(assertThrows(JsonValueException.class, () -> JsonArray.of(
+ List.of(JsonNumber.of("1.5"))).get(0).asBoolean()).getMessage().contains("Path"));
+ assertFalse(assertThrows(JsonValueException.class, () -> JsonArray.of(
+ List.of(JsonString.of("foo"))).get(0).asBoolean()).getMessage().contains("Path"));
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestGenerate.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestGenerate.java
new file mode 100644
index 00000000..804a1066
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestGenerate.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import java.util.List;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import org.junit.jupiter.api.Test;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TestGenerate extends JsonTestLoggingConfig {
+
+ private static final String SRC =
+ """
+ [
+ { "name": "John", "age": 30, "city": "New-York" },
+ { "name": "Jane", "age": 20, "city": "Boston" },
+ true,
+ false,
+ null,
+ [ "array", "inside", {"inner-obj": true, "top-level": false}],
+ "foo",
+ 42
+ ]
+ """;
+
+ @Test
+ void testToString() {
+ var jv = Json.parse(SRC);
+ var result = jv.toString();
+ var expected = SRC.replaceAll("[\n ]", "");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void testToDisplayString_NullIndent() {
+ assertThrows(NullPointerException.class,
+ () -> Json.toDisplayString(JsonString.of("foo"), null));
+ }
+
+ @Test
+ void testToDisplayString_InvalidIndent() {
+ assertThrows(IllegalArgumentException.class,
+ () -> Json.toDisplayString(JsonString.of("foo"), "abc"));
+ }
+
+ static Stream DISPLAYSTRING() { return List.of(
+ Arguments.of("",
+ """
+ [
+ {
+ "name": "John",
+ "age": 30,
+ "city": "New-York"
+ },
+ {
+ "name": "Jane",
+ "age": 20,
+ "city": "Boston"
+ },
+ true,
+ false,
+ null,
+ [
+ "array",
+ "inside",
+ {
+ "inner-obj": true,
+ "top-level": false
+ }
+ ],
+ "foo",
+ 42
+ ]"""),
+ Arguments.of(" ",
+ """
+ [
+ {
+ "name": "John",
+ "age": 30,
+ "city": "New-York"
+ },
+ {
+ "name": "Jane",
+ "age": 20,
+ "city": "Boston"
+ },
+ true,
+ false,
+ null,
+ [
+ "array",
+ "inside",
+ {
+ "inner-obj": true,
+ "top-level": false
+ }
+ ],
+ "foo",
+ 42
+ ]"""),
+ Arguments.of(" ",
+ """
+ [
+ {
+ "name": "John",
+ "age": 30,
+ "city": "New-York"
+ },
+ {
+ "name": "Jane",
+ "age": 20,
+ "city": "Boston"
+ },
+ true,
+ false,
+ null,
+ [
+ "array",
+ "inside",
+ {
+ "inner-obj": true,
+ "top-level": false
+ }
+ ],
+ "foo",
+ 42
+ ]"""),
+ Arguments.of("\t\r\n\u0020 ",
+ """
+ [
+ \t\r\n\u0020 {
+ \t\r\n\u0020 \t\r\n\u0020 "name": "John",
+ \t\r\n\u0020 \t\r\n\u0020 "age": 30,
+ \t\r\n\u0020 \t\r\n\u0020 "city": "New-York"
+ \t\r\n\u0020 },
+ \t\r\n\u0020 {
+ \t\r\n\u0020 \t\r\n\u0020 "name": "Jane",
+ \t\r\n\u0020 \t\r\n\u0020 "age": 20,
+ \t\r\n\u0020 \t\r\n\u0020 "city": "Boston"
+ \t\r\n\u0020 },
+ \t\r\n\u0020 true,
+ \t\r\n\u0020 false,
+ \t\r\n\u0020 null,
+ \t\r\n\u0020 [
+ \t\r\n\u0020 \t\r\n\u0020 "array",
+ \t\r\n\u0020 \t\r\n\u0020 "inside",
+ \t\r\n\u0020 \t\r\n\u0020 {
+ \t\r\n\u0020 \t\r\n\u0020 \t\r\n\u0020 "inner-obj": true,
+ \t\r\n\u0020 \t\r\n\u0020 \t\r\n\u0020 "top-level": false
+ \t\r\n\u0020 \t\r\n\u0020 }
+ \t\r\n\u0020 ],
+ \t\r\n\u0020 "foo",
+ \t\r\n\u0020 42
+ ]""")
+ ).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("DISPLAYSTRING")
+ void testDisplayString(String indent, String expected) {
+ assertEquals(expected, Json.toDisplayString(Json.parse(SRC), indent));
+ }
+
+ @Test
+ void testEscapesMemberNames() {
+ var json = Json.parse("{ \"a\\\"b\" : null }");
+ var display = Json.toDisplayString(json, " ");
+
+ assertEquals("""
+ {
+ "a\\\"b": null
+ }""", display);
+
+ assertDoesNotThrow(() -> Json.parse(display));
+ }
+
+ @Test
+ void testDeepNestingToString() {
+ assertDoesNotThrow(() -> deepNest().toString());
+ }
+
+ @Test
+ void testDeepNestingToDisplayString() {
+ assertDoesNotThrow(() -> Json.toDisplayString(deepNest(), ""));
+ }
+
+ private static JsonValue deepNest() {
+ int depth = 10_000;
+ JsonValue jv = JsonNumber.of(0);
+ for (int i = 0; i < depth; i++) {
+ jv = JsonArray.of(List.of(jv));
+ }
+ return jv;
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonArray.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonArray.java
new file mode 100644
index 00000000..ae731e8f
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonArray.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestJsonArray extends JsonTestLoggingConfig {
+
+ @Nested
+ class TestParse {
+
+ // Some basic malformed JSON arrays and expected error message
+ static Stream BASIC_FAIL() { return List.of(
+ Arguments.of("[ \"foo\" ",
+ "JSON Array is not closed with a bracket. Path: \"[\". Location: line 0, position 9."),
+ Arguments.of("[ \"foo\", ",
+ "Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"[1\". Location: line 0, position 10."),
+ Arguments.of("[ ",
+ "JSON Array is not closed with a bracket. Path: \"[\". Location: line 0, position 2."),
+ Arguments.of("null ]",
+ "Additional value(s) were found after the JSON Value. Path: \"\". Location: line 0, position 5."),
+ Arguments.of("[ [ [ 0, 1, two ] ] ]",
+ "Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"[0[0[2\". Location: line 0, position 13.")).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("BASIC_FAIL")
+ void basicFailParse(String json, String expected) {
+ var e = assertThrows(JsonParseException.class, () -> Json.parse(json),
+ "String parse did not fail for %s".formatted(json));
+ assertEquals(expected, e.getMessage());
+ e = assertThrows(JsonParseException.class, () -> Json.parse(json.toCharArray()),
+ "Char parse did not fail for %s".formatted(json));
+ assertEquals(expected, e.getMessage());
+ }
+ }
+
+ @Nested
+ class TestFactory {
+
+ // Ensure equivalence of JsonArray created from parse vs of factory
+ @Test
+ void testFactory() {
+
+ var doc = Json.parse(
+ """
+ [1, "two", false, null, {"name": 42}, [1]]
+ """);
+
+ var expected = JsonArray.of(
+ List.of(
+ JsonNumber.of(1),
+ JsonString.of("two"),
+ JsonBoolean.of(Boolean.FALSE),
+ JsonNull.of(),
+ JsonObject.of(Map.of("name", JsonNumber.of(42))),
+ JsonArray.of(List.of(JsonNumber.of(1)))
+ )
+ ).asList();
+ if (doc instanceof JsonArray ja) {
+ //only compare types
+ compareTypes(expected, ja.asList());
+ } else {
+ throw new RuntimeException("JsonArray expected");
+ }
+ }
+
+ private static void compareTypes(List expected, List actual) {
+ assertEquals(expected.size(), actual.size());
+ for (int index = 0; index < expected.size(); index++) {
+ assertEquals(expected.get(index).getClass(), actual.get(index).getClass());
+ }
+ }
+
+ @Test
+ void immutabilityOfTest() {
+ var list = new ArrayList();
+ list.add(JsonString.of("foo"));
+ var ja = JsonArray.of(list);
+ assertEquals(1, ja.asList().size());
+ // Modifications to backed list should not change JsonArray
+ list.add(JsonString.of("foo"));
+ assertEquals(1, ja.asList().size());
+ // Modifications to JsonArray asList() should throw
+ assertThrows(UnsupportedOperationException.class,
+ () -> ja.asList().add(JsonNull.of()),
+ "Array values able to be modified");
+ }
+
+ @Test
+ void nullTest() {
+ // null list to of factory
+ assertThrows(NullPointerException.class, () -> JsonArray.of(null));
+ List list = new ArrayList<>();
+ list.add(null);
+ // JsonArray.of() should throw as typed to JsonValue
+ assertThrows(NullPointerException.class, () -> JsonArray.of(list));
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
new file mode 100644
index 00000000..a543578a
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonParseException;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestJsonLiteral extends JsonTestLoggingConfig {
+
+ void conversionTest() {
+ assertTrue(JsonBoolean.of(true).asBoolean());
+ assertFalse(JsonBoolean.of(false).asBoolean());
+ }
+
+ @Nested
+ class TestParse {
+
+ @ParameterizedTest
+ @MethodSource("BASIC_VALID")
+ void basicValidParse(String json) {
+ assertDoesNotThrow(() -> Json.parse(json),
+ "String parse failed for %s".formatted(json));
+ assertDoesNotThrow(() -> Json.parse(json.toCharArray()),
+ "Char parse failed for %s".formatted(json));
+ }
+
+ // Basic JSON primitives
+ static Stream BASIC_VALID() { return Stream.of("%s", " %s", "%s ", " %s ", "[%s]", "{\"foo\":%s}")
+ .flatMap(s -> Stream.of("true", "false", "null").map(s::formatted)).toList().stream(); }
+
+ @ParameterizedTest
+ @MethodSource("BASIC_FAIL")
+ void basicFailParse(String json) {
+ assertThrows(JsonParseException.class, () -> Json.parse(json),
+ "String parse did not fail for %s".formatted(json));
+ assertThrows(JsonParseException.class, () -> Json.parse(json.toCharArray()),
+ "Char parse did not fail for %s".formatted(json));
+ }
+
+ // Basic JSON primitives and expected parse exception message
+ static Stream BASIC_FAIL() { return List.of(
+ // Null
+ Arguments.of("nul", "Expected null"),
+ Arguments.of("n", "Expected null"),
+ // Boolean
+ Arguments.of("fals", "Expected false"),
+ Arguments.of("f", "Expected false"),
+ Arguments.of("tru", "Expected true"),
+ Arguments.of("t", "Expected true")
+ ).stream(); }
+ }
+
+ @Nested
+ class TestFactory {
+
+ @Test
+ void booleanOfTest() {
+ assertTrue(JsonBoolean.of(true).asBoolean());
+ assertFalse(JsonBoolean.of(false).asBoolean());
+ }
+
+ @Test
+ void nullOfTest() {
+ assertTrue(JsonNull.of() instanceof JsonNull);
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumber.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumber.java
new file mode 100644
index 00000000..24f072d7
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumber.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import java.util.List;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonValueException;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonParseException;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestJsonNumber extends JsonTestLoggingConfig {
+
+ @Nested
+ class TestValue {
+
+ @ParameterizedTest
+ @MethodSource
+ void testUniformRepresentations(String str, double db, long l, int i) {
+ var json = Json.parse(str);
+ assertEquals(db, json.asDouble());
+ assertEquals(l, json.asLong());
+ assertEquals(i, json.asInt());
+ json = Json.parse("-" + str);
+ assertEquals(-db, json.asDouble());
+ assertEquals(-l, json.asLong());
+ assertEquals(-i, json.asInt());
+ }
+
+ private static Stream testUniformRepresentations() {
+ return Stream.of(
+ Arguments.of("5", 5d, 5L, 5),
+ Arguments.of("5.0", 5d, 5L, 5),
+ Arguments.of("5.00", 5d, 5L, 5),
+ Arguments.of("5e0", 5d, 5L, 5),
+ Arguments.of("5e+0", 5d, 5L, 5),
+ Arguments.of("5e-0", 5d, 5L, 5),
+ Arguments.of("5e3", 5e3, 5000L, 5000),
+ Arguments.of("50e-1", 50e-1, 5L, 5),
+ Arguments.of("50.0e-1", 50.0e-1, 5L, 5),
+ Arguments.of("555.5e5", 555.5e5, 55550000L, 55550000),
+ Arguments.of("555.5e1", 555.5e1, 5555L, 5555),
+ Arguments.of("0e999999999999", 0d, 0L, 0),
+ Arguments.of("0.0e-999999999999", 0d, 0L, 0)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testDoubleRepresentation(String str, double d) {
+ var json = Json.parse(str);
+ assertEquals(d, json.asDouble());
+ assertThrows(JsonValueException.class, json::asLong);
+ json = Json.parse("-" + str);
+ assertEquals(-d, json.asDouble());
+ assertThrows(JsonValueException.class, json::asLong);
+ }
+
+ private static Stream testDoubleRepresentation() {
+ return Stream.of(
+ Arguments.of("0.01", 0.01),
+ Arguments.of("0.3232e-3", 0.3232e-3),
+ Arguments.of("0.55", 0.55),
+ Arguments.of("55.55", 55.55),
+ Arguments.of("5e-5", 5e-5),
+ Arguments.of("5.55e-5", 5.55e-5),
+ Arguments.of("5.00e-5", 5.00e-5),
+ Arguments.of("5e100", 5e100),
+ Arguments.of("55.55e1", 55.55e1),
+ Arguments.of("55.55e+1", 55.55e1),
+ Arguments.of("1.7976931348623157E308", 1.7976931348623157E308),
+ Arguments.of("9223372036854775999", 9223372036854775999d),
+ Arguments.of("9223372036854775999e0", 9223372036854775999d),
+ Arguments.of("5e-100", 5e-100),
+ Arguments.of("5.000e-100", 5e-100),
+ Arguments.of("1e-999999999999", 0.0)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testLongRepresentation(String str, long l) {
+ var json = Json.parse(str);
+ assertEquals(l, json.asLong());
+ assertDoesNotThrow(json::asDouble);
+ json = Json.parse("-" + str);
+ assertEquals(-l, json.asLong());
+ assertDoesNotThrow(json::asDouble);
+ }
+
+ private static Stream testLongRepresentation() {
+ return Stream.of(
+ Arguments.of("9007199254740993", 9007199254740993L),
+ Arguments.of("9007199254740993.0", 9007199254740993L),
+ Arguments.of("9007199254740993.0e0", 9007199254740993L),
+ Arguments.of("9223372036854775807", 9223372036854775807L),
+ Arguments.of("9223372036854775807.0", 9223372036854775807L),
+ Arguments.of("9223372036854775807.0e0", 9223372036854775807L),
+ Arguments.of("9223372036854775807.0e-0", 9223372036854775807L),
+ Arguments.of("92233720368547758070.0e-1", 9223372036854775807L),
+ Arguments.of("922337203685477580700.0e-2", 9223372036854775807L)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testIntRepresentation(String str, int i) {
+ var json = Json.parse(str);
+ assertEquals(i, json.asInt());
+ assertDoesNotThrow(json::asLong);
+ assertDoesNotThrow(json::asDouble);
+ json = Json.parse("-" + str);
+ assertEquals(-i, json.asInt());
+ assertDoesNotThrow(json::asLong);
+ assertDoesNotThrow(json::asDouble);
+ }
+
+ private static Stream testIntRepresentation() {
+ return Stream.of(
+ Arguments.of("2147483647", Integer.MAX_VALUE),
+ Arguments.of("2147483647.0", Integer.MAX_VALUE),
+ Arguments.of("2147483647.0e0", Integer.MAX_VALUE)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testDoubleOutOfRange(String str) {
+ var json = Json.parse(str);
+ assertThrows(JsonValueException.class, json::asDouble);
+ }
+
+ private static Stream testDoubleOutOfRange() {
+ return Stream.of(
+ Arguments.of("9e111111111111"),
+ Arguments.of("-9e111111111111"),
+ Arguments.of("1.7976931348623159E308"),
+ Arguments.of("-1.7976931348623159E308")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testLongOutOfRange(String str) {
+ var json = Json.parse(str);
+ assertThrows(JsonValueException.class, json::asLong);
+ }
+
+ private static Stream testLongOutOfRange() {
+ return Stream.of(
+ Arguments.of("9e111111111111"),
+ Arguments.of("-9e111111111111"),
+ Arguments.of("9223372036854775808"),
+ Arguments.of("-9223372036854775809")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testIntOutOfRange(String str) {
+ var json = Json.parse(str);
+ assertThrows(JsonValueException.class, json::asInt);
+ }
+
+ private static Stream testIntOutOfRange() {
+ return Stream.of(
+ Arguments.of("2147483648"),
+ Arguments.of("-2147483649")
+ );
+ }
+ }
+
+ @Nested
+ class TestParse {
+
+ @ParameterizedTest
+ @MethodSource("parseCases")
+ void testToString_Parsed(String src) {
+ // assert their toString() returns the original text
+ assertEquals(src, Json.parse(src).toString());
+ }
+
+ private static Stream parseCases() {
+ return Stream.of(
+ Arguments.of("1"),
+ Arguments.of("0"),
+ Arguments.of("9223372036854775807"),
+ Arguments.of("-9223372036854775808"),
+ Arguments.of("1.0"),
+ Arguments.of("9223372036854775807.0"),
+ Arguments.of("-9223372036854775808.0"),
+ Arguments.of("9223372036854775807e0"),
+ Arguments.of("-9223372036854775807e0"),
+ Arguments.of("1e0"),
+ Arguments.of("1e-0"),
+ Arguments.of("0.0"),
+ Arguments.of("-0.0"),
+ Arguments.of("0e0"),
+ Arguments.of("0e1"),
+ Arguments.of("0e-0"),
+ Arguments.of("0e-1"),
+ Arguments.of("5.5e1"),
+ Arguments.of("1.0e1"),
+ Arguments.of("1.0"),
+ Arguments.of("1.000"),
+ Arguments.of("0.001e3"),
+ Arguments.of("5.5"),
+ Arguments.of("4.9999999"),
+ Arguments.of("4.999999999999999999999999999999999999"),
+ Arguments.of("9007199254740989.5"),
+ Arguments.of("9007199254740990.999999999999"),
+ Arguments.of("0.0000123E-0000000045"),
+ Arguments.of("5."+"5".repeat(17)),
+ Arguments.of("55.55e1"),
+ Arguments.of("1e-1"),
+ Arguments.of("9223372036854775806.5"),
+ Arguments.of("-9223372036854775807.5"),
+ Arguments.of("4.9E-324"),
+ Arguments.of("9223372036854775807.5e0"),
+ Arguments.of("9223372036854775808e0"),
+ Arguments.of("1.7976931348623157E308")
+ );
+ }
+
+ private static Stream testToStringEquality() {
+ return Stream.of(
+ Arguments.of("3", "3"),
+ Arguments.of("3", " 3 "),
+ Arguments.of("3.0", "3.0"),
+ Arguments.of("3e0", "3E0"),
+ Arguments.of("3.141592653589793238462643383279", "3.141592653589793238462643383279"),
+ Arguments.of("3", "3.0"),
+ Arguments.of("3.0", "3.000"),
+ Arguments.of("3", "3e0"),
+ Arguments.of("0.0", "-0.0"),
+ Arguments.of("3", "4"),
+ Arguments.of("3.0", "3.1"),
+ Arguments.of("3", "3.1"),
+ Arguments.of("3.0", "3.001"),
+ Arguments.of("3", "3e1"),
+ Arguments.of("3.141592653589793238462643383279", "3.141592653589793238462643383278")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testToStringEquality(String arg1, String arg2) {
+ var jv1 = Json.parse(arg1);
+ var jv2 = Json.parse(arg2);
+
+ // assert their toString() returns the original text (w/o leading/trailing spaces)
+ var a1 = arg1.trim();
+ var a2 = arg2.trim();
+ assertEquals(a1, jv1.toString());
+ assertEquals(a2, jv2.toString());
+ }
+
+ static Stream INVALID_NUMBER() { return List.of(
+ Arguments.of("00", "Invalid position of '0' within JSON Number. Path: \"\". Location: line 0, position 1."),
+ Arguments.of("-00", "Invalid position of '0' within JSON Number. Path: \"\". Location: line 0, position 2."),
+ Arguments.of("01", "Invalid position of '0' within JSON Number. Path: \"\". Location: line 0, position 1."),
+ Arguments.of("5e-2+2", "Invalid position of '+' within JSON Number. Path: \"\". Location: line 0, position 4."),
+ Arguments.of("+5", "Invalid position of '+' within JSON Number. Path: \"\". Location: line 0, position 0."),
+ Arguments.of("5e+2-2", "Invalid position of '-' within JSON Number. Path: \"\". Location: line 0, position 4."),
+ Arguments.of("5e2+", "Invalid position of '+' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("5e2+2", "Invalid position of '+' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("5e2-", "Invalid position of '-' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("5e2-2", "Invalid position of '-' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of(".5", "Invalid position of '.' within JSON Number. Path: \"\". Location: line 0, position 0."),
+ Arguments.of("5e.2", "Invalid position of '.' within JSON Number. Path: \"\". Location: line 0, position 2."),
+ Arguments.of("5.5.5", "Invalid position of '.' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("5e3e", "Invalid position of 'e' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("e2", "Invalid position of 'e' within JSON Number. Path: \"\". Location: line 0, position 0."),
+ Arguments.of("e", "Invalid position of 'e' within JSON Number. Path: \"\". Location: line 0, position 0."),
+ Arguments.of("5.", "Input expected after '[.|e|E]'. Path: \"\". Location: line 0, position 2."),
+ Arguments.of("5e", "Input expected after '[.|e|E]'. Path: \"\". Location: line 0, position 2."),
+ Arguments.of("5e5.5", "Invalid position of '.' within JSON Number. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("5.5e5.5", "Invalid position of '.' within JSON Number. Path: \"\". Location: line 0, position 5.")
+ ).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("INVALID_NUMBER")
+ void testMessages(String json, String err) {
+ Exception e = assertThrows(JsonParseException.class, () -> Json.parse(json));
+ assertEquals(err, e.getMessage());
+ }
+ }
+
+ @Nested
+ class TestFactory {
+
+ @Test
+ void testInfinity() {
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.POSITIVE_INFINITY));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NEGATIVE_INFINITY));
+ }
+
+ @Test
+ void testNan() {
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NaN));
+ // parse test not required for Nan, cannot parse "NaN"
+ }
+
+ @Test
+ void testToString_factory() {
+ assertEquals("42", JsonNumber.of((byte)42).toString());
+ assertEquals("42", JsonNumber.of((short)42).toString());
+ assertEquals("42", JsonNumber.of(42).toString());
+ assertEquals("42", JsonNumber.of(42L).toString());
+ assertEquals(JsonNumber.of(Integer.MAX_VALUE).toString(), Integer.valueOf(Integer.MAX_VALUE).toString());
+ assertEquals(JsonNumber.of(Long.MAX_VALUE).toString(), Long.valueOf(Long.MAX_VALUE).toString());
+ assertEquals(JsonNumber.of(0.1f).toString(), Double.valueOf(0.1f).toString());
+ assertEquals("0.1", JsonNumber.of(0.1d).toString());
+ assertEquals("42.0", JsonNumber.of(42.0d).toString());
+ assertEquals("42.0", JsonNumber.of(420e-1).toString());
+ assertEquals("4.2E7", JsonNumber.of(42e6).toString());
+ assertEquals(JsonNumber.of(Double.MAX_VALUE).toString(), Double.valueOf(Double.MAX_VALUE).toString());
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("foo"));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("true"));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("\"foo\""));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("null"));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("[1, 2]"));
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("{\"foo\": 42}"));
+ }
+
+ @Test
+ void testRoundTrip() {
+ // factories
+
+ // int
+ assertEquals(42, JsonNumber.of((byte)42).asInt());
+ assertEquals(42, JsonNumber.of((short)42).asInt());
+ assertEquals(42, JsonNumber.of(42).asInt());
+ assertEquals(42, JsonNumber.of(42L).asInt());
+ assertEquals(42, JsonNumber.of(42.0d).asInt());
+ assertEquals(42, JsonNumber.of(420e-1).asInt());
+ assertEquals(42_000_000, JsonNumber.of(42e6).asInt());
+ assertEquals(42, JsonNumber.of("42").asInt());
+ assertEquals(Integer.MAX_VALUE, JsonNumber.of(Integer.MAX_VALUE).asInt());
+ assertEquals(Integer.MAX_VALUE, JsonNumber.of("2147483647").asInt());
+
+ // long
+ assertEquals(42L, JsonNumber.of((byte)42).asLong());
+ assertEquals(42L, JsonNumber.of((short)42).asLong());
+ assertEquals(42L, JsonNumber.of(42).asLong());
+ assertEquals(42L, JsonNumber.of(42L).asLong());
+ assertEquals(42L, JsonNumber.of(42.0d).asLong());
+ assertEquals(42L, JsonNumber.of(420e-1).asLong());
+ assertEquals(42_000_000L, JsonNumber.of(42e6).asLong());
+ assertEquals(42L, JsonNumber.of("42").asLong());
+ assertEquals(Long.MAX_VALUE, JsonNumber.of("9223372036854775807").asLong());
+ assertEquals(Long.MAX_VALUE, JsonNumber.of(Long.MAX_VALUE).asLong());
+
+ // double
+ assertEquals((double)0.1f, JsonNumber.of(0.1f).asDouble());
+ assertEquals(0.1d, JsonNumber.of(0.1d).asDouble());
+ assertEquals(1d, JsonNumber.of(1e0).asDouble());
+ assertEquals(Double.MAX_VALUE, JsonNumber.of(Double.MAX_VALUE).asDouble());
+ assertEquals(0.1d, JsonNumber.of("0.1").asDouble());
+ assertEquals(1d, JsonNumber.of("1e0").asDouble());
+ assertEquals(Double.MAX_VALUE, JsonNumber.of("1.7976931348623157E308").asDouble());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void factoryTest(Number n) {
+ // Java 21 backport: boxed type patterns (upstream uses primitive
+ // type patterns, a Java 23+ preview feature, with @enablePreview)
+ var str = switch (n) {
+ case Byte b -> JsonNumber.of(b).toString();
+ case Short s -> JsonNumber.of(s).toString();
+ case Integer i -> JsonNumber.of(i).toString();
+ case Long l -> JsonNumber.of(l).toString();
+ case Float f -> JsonNumber.of(f).toString();
+ case Double d -> JsonNumber.of(d).toString();
+ default -> throw new IllegalArgumentException("incorrect test argument");
+ };
+ var expected = switch (n) {
+ case Byte b -> Long.toString(b);
+ case Short s -> Long.toString(s);
+ case Integer i -> Long.toString(i);
+ case Long l -> Long.toString(l);
+ case Float f -> Double.toString(f);
+ case Double d -> Double.toString(d);
+ default -> throw new IllegalArgumentException("incorrect test argument");
+ };
+ assertEquals(str, expected);
+ }
+
+ private static Stream factoryTest() {
+ return Stream.of(
+ Arguments.of((byte)1),
+ Arguments.of((short)1),
+ Arguments.of((int)1),
+ Arguments.of(1L),
+ Arguments.of(1.0000000596046448f), // 1.0000001f -> 1.0000001192092896d
+ Arguments.of(1.0000000596046448d)
+ );
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonObject.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonObject.java
new file mode 100644
index 00000000..de803d85
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonObject.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestJsonObject extends JsonTestLoggingConfig {
+
+ private static final String JSON_WITH_SPACES =
+ """
+ [
+ { "name": "John", "age": 30, "city": "New York" },
+ { "name": "Jane", "age": 20, "city": "Boston" },
+ true,
+ false,
+ null,
+ [ "array", "inside", {"inner obj": true, "top-level": false}],
+ "foo",
+ 42
+ ]
+ """;
+
+ private static final String JSON_NO_NEWLINE =
+ """
+ [{"name":"John","age":30,"city":"New York"},{"name":"Jane","age":20,"city":"Boston"},true,false,null,["array","inside",{"inner obj":true,"top-level":false}],"foo",42]""";
+
+ @Nested
+ class TestParse {
+
+ // Ensure storage is done with the unescaped version
+ @Test
+ void retrievalTest() {
+ // parse
+ var jo = (JsonObject) Json.parse("{ \"foo\\t\" : false}");
+ assertFalse(jo.asMap().get("foo\t").asBoolean());
+ jo = (JsonObject) Json.parse("{ \"foo\\u0009\" : false}");
+ assertFalse(jo.asMap().get("foo\t").asBoolean());
+ // jo factory
+ jo = JsonObject.of(Map.of("foo\t", JsonBoolean.of(false)));
+ assertFalse(jo.asMap().get("foo\t").asBoolean());
+ }
+
+ @Test
+ void toStringTest() {
+ // 2 char sequence first
+ var key = "\" \\t \\u0021 \\u0022 \\u005c \\u0008 test \"";
+ var map = "{" + key + ":null}";
+ assertEquals("{\" \\t ! \\\" \\\\ \\b test \":null}", Json.parse(map).toString());
+ // Unicode escape sequence first
+ var key2 = "\" \\u0021 \\t \\u0022 \\u005c \\u0008 test \"";
+ var map2 = "{" + key2 + ":null}";
+ assertEquals("{\" ! \\t \\\" \\\\ \\b test \":null}", Json.parse(map2).toString());
+ }
+
+ // Check for basic duplicate name
+ @Test
+ void testDuplicateKeys() {
+ var json =
+ """
+ { "clone": "bob", "clone": "foo" }
+ """;
+ assertThrows(JsonParseException.class, () -> Json.parse(json));
+ }
+
+ // https://datatracker.ietf.org/doc/html/rfc8259#section-8.3
+ // Check for equality via unescaped value
+ @Test
+ void testDuplicateKeyEqualityUnescaped() {
+ var json =
+ """
+ { "clone": "bob", "clon\\u0065": "foo" }
+ """;
+ assertThrows(JsonParseException.class, () -> Json.parse(json));
+ }
+
+ @Test
+ void testDuplicateKeyEqualityMultipleUnescaped() {
+ var json =
+ """
+ { "clonee": "bob", "clon\\u0065\\u0065": "foo" }
+ """;
+ assertThrows(JsonParseException.class, () -> Json.parse(json));
+ }
+
+ @Test
+ void testDuplicateKeyEqualityUnescapedVariant() {
+ var json =
+ """
+ { "c\\b": "bob", "c\b": "foo" }
+ """;
+ assertThrows(JsonParseException.class, () -> Json.parse(json));
+ }
+
+ static Stream INVALID_OBJECTS() { return List.of(
+ "{ :name\": \"Brian\"}",
+ "{ \"name:: \"Brian\"}",
+ "{ \"name\": :Brian\"}",
+ "{ \"name\": \"Brian:}",
+ "{ \"name\": ,Brian\"}",
+ "{ foo \"name\": \"Brian\"}", // Garbage before name
+ "{ \"name\" foo : \"Brian\"}", // Garbage after name, but before colon
+ // Garbage in second name/val
+ "{ \"name\": \"Brian\" , \"name2\": \"Brian\" 5}",
+ "{ \"name\": \"Brian\" 5}", // Garbage next to closing bracket
+ "{ \"name\": \"Brian\"5 }", // Garbage next to value
+ "{ \"name\": \"Brian\" 5 }", // Garbage with ws
+ // Other cases, where non index based JsonValue occurs first
+ "{ \"name\": 5 \"Brian\" }",
+ "{ \"name\": 5 null }",
+ // Garbage after JsonValue in the form of index based JsonValue
+ "{ \"name\": \"Brian\" { \"name2\": \"another String\"} }",
+ "{ \"name\": \"Brian\" [\"another String\"] }",
+ "{ \"name\": \"Brian\" \"another String\"}").stream(); }
+
+ @ParameterizedTest
+ @MethodSource("INVALID_OBJECTS")
+ void malformedObjectParseTest(String badJson) {
+ assertThrows(JsonParseException.class, () -> Json.parse(badJson));
+ }
+
+ static Stream INVALID_OBJECTS_MESSAGES() { return List.of(
+ Arguments.of("{ \"foo\" : ", "Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"{foo\". Location: line 0, position 10."),
+ Arguments.of("{ \"foo\" ", "Expected a colon after the member name. Path: \"{\". Location: line 0, position 8."),
+ Arguments.of("{ \"foo\" : \"bar\" ", "JSON Object is not closed with a brace. Path: \"{\". Location: line 0, position 16."),
+ Arguments.of("{ \"foo\" : \"bar\", ", "JSON Object is not closed with a brace. Path: \"{\". Location: line 0, position 18."),
+ Arguments.of("{ \"foo\" : 1, \"foo\" : 1 ", "Duplicate member name: \"foo\" was already parsed. Path: \"{\". Location: line 0, position 13."),
+ Arguments.of("{ foo : \"bar\" ", "Expecting a JSON Object member name. Path: \"{\". Location: line 0, position 2."),
+ Arguments.of("{ \"foo : ", "JSON Object member name is not closed with a quotation mark. Path: \"{\". Location: line 0, position 9."),
+ Arguments.of("{ ", "JSON Object is not closed with a brace. Path: \"{\". Location: line 0, position 2."),
+
+ // Escaped names
+ Arguments.of("{ \"foo\" : null, \"\\u0066oo\" : null ", "Duplicate member name: \"foo\" was already parsed. Path: \"{\". Location: line 0, position 16."),
+ Arguments.of("{ \"\\u00M\" ", "Invalid Unicode escape sequence. 'M' is not a hex digit. Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"\\u00\f\" ", "Invalid Unicode escape sequence. '\\u000C' is not a hex digit. Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"\\u00\u0020\" ", "Invalid Unicode escape sequence. '\\u0020' is not a hex digit. Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"\\u00\u2028\" ", "Invalid Unicode escape sequence. '\\u2028' is not a hex digit. Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"foo\\n\" : null, \"foo\\n\" : null ", "Duplicate member name: \"foo\\n\" was already parsed. Path: \"{\". Location: line 0, position 18."),
+ Arguments.of("{ \"foo\\a\" ", "Unrecognized escape sequence: \"\\a\". Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"foo\\\f\" ", "Unrecognized escape sequence: \"\\\\u000C\". Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"foo\\\u0020\" ", "Unrecognized escape sequence: \"\\\\u0020\". Path: \"{\". Location: line 0, position 7."),
+ Arguments.of("{ \"foo\\\u2028\" ", "Unrecognized escape sequence: \"\\\\u2028\". Path: \"{\". Location: line 0, position 7."),
+
+ // multi-line duplicate member for error location validation
+ Arguments.of("""
+ {
+ "a": 0,
+ "a": [
+ ]
+ }
+ """, "Duplicate member name: \"a\" was already parsed. Path: \"{\". Location: line 2, position 4."),
+ Arguments.of("""
+ {
+ "a": 0,
+ "a"
+ : 1
+ }
+ """, "Duplicate member name: \"a\" was already parsed. Path: \"{\". Location: line 2, position 4."),
+
+ // nested
+ Arguments.of("{ \"l1\": { \"l2\": [ 0, 1, two ] } }",
+ "Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"{l1{l2[2\". Location: line 0, position 25."),
+ Arguments.of("{\"ba\\\"zz\": [ invalid ]}",
+ "Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"{ba\\\"zz[0\". Location: line 0, position 13."),
+ Arguments.of("{\"\\u0061\": [ invalid ]}",
+ "Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"{\\u0061[0\". Location: line 0, position 13.")
+ ).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("INVALID_OBJECTS_MESSAGES")
+ void testMessages(String json, String err) {
+ Exception e = assertThrows(JsonParseException.class, () -> Json.parse(json));
+ assertEquals(err, e.getMessage());
+ }
+
+ private static final String JSON_EXTRA_SPACES =
+ """
+ [
+ \s
+ { "name" : "John", "age" : 30, "city": "New York" },
+ { "name": "Jane" , "age": 20, "city": "Boston" },
+ \s
+ \s
+ true, \s
+ false ,
+ null, \s
+ [ "array" , "inside", {"inner obj": true, "top-level" : false } ] ,\s
+ "foo",\s
+ 42
+ ]
+ \s""";
+
+ // White space is allowed but should have no effect
+ // on the underlying structure, and should not play a role during equality
+ @Test
+ void testWhiteSpaceEquality() {
+ var obj = Json.parse(JSON_EXTRA_SPACES);
+ var str = assertDoesNotThrow(() -> obj.toString()); // build the map/arr
+ var expStr = Json.parse(JSON_WITH_SPACES).toString();
+ // Ensure equivalent Json (besides white space) generates equivalent
+ // toString values
+ assertEquals(expStr, str);
+ }
+
+ @Test
+ void orderingParseTest() {
+ assertEquals(JSON_NO_NEWLINE, Json.parse(JSON_WITH_SPACES).toString());
+ }
+
+ @Test
+ void testToDisplayStringOrder() {
+ var json = """
+ {
+ "a": 1,
+ "c": 2,
+ "b": 3
+ }""";
+ assertEquals(json, Json.toDisplayString(Json.parse(json), " "));
+ }
+
+ // Ensure decoded escape sequences are translated to valid JSON
+ // Supported 2 char escapes should be translated, otherwise U sequence
+ // needs to be preserved.
+ @Test
+ void controlCodeRoundTripTest() {
+ for (int i = 0; i < 32; i++) {
+ var mapWithSequence = "{ \" \\u" + String.format("%04x", i) + "\" : true }";
+ Json.parse(Json.parse(mapWithSequence).toString());
+ }
+ }
+ }
+
+ @Nested
+ class TestFactory {
+
+ private static final String JSON_OBJ =
+ """
+ { "name": "Brian", "shoeSize": 10 }
+ """;
+
+ private static final String SMALL_JSON_OBJ =
+ """
+ { "shoeSize": 10 }
+ """;
+
+ private static final String EMPTY_JSON_OBJ =
+ """
+ { }
+ """;
+
+ @Test
+ void emptyBuildTest() {
+ var expectedJson = Json.parse(JSON_OBJ);
+ var builtJson = new HashMap();
+ builtJson.put("name", JsonString.of("Brian"));
+ builtJson.put("shoeSize", JsonNumber.of(10));
+ compareValueTypes(((JsonObject)expectedJson).asMap(), JsonObject.of(builtJson).asMap());
+ }
+
+ @Test
+ void existingBuildTest() {
+ var sourceJson = Json.parse(JSON_OBJ);
+ var builtJson = JsonObject.of(((JsonObject)sourceJson).asMap());
+ compareValueTypes(((JsonObject)sourceJson).asMap(), builtJson.asMap());
+ }
+
+ @Test
+ void removalTest() {
+ var expectedJson = Json.parse(SMALL_JSON_OBJ);
+ var sourceJson = Json.parse(JSON_OBJ);
+ var builtJson = new HashMap<>(((JsonObject) sourceJson).asMap());
+ builtJson.remove("name");
+ compareValueTypes(((JsonObject)expectedJson).asMap(), builtJson);
+ }
+
+ @Test
+ void clearTest() {
+ var expectedJson = Json.parse(EMPTY_JSON_OBJ);
+ var builtJson = JsonObject.of(Map.of());
+ compareValueTypes(((JsonObject)expectedJson).asMap(), builtJson.asMap());
+ }
+
+ // Basic test to check of factory for JsonObject
+ @Test
+ void ofFactoryTest() {
+ HashMap map = new HashMap<>();
+ map.put("foo", JsonNumber.of(5));
+ map.put("bar", JsonString.of("value"));
+ map.put("baz", JsonNull.of());
+ compareValueTypes(JsonObject.of(map).asMap(),
+ ((JsonObject)Json.parse("{ \"foo\" : 5, \"bar\" : \"value\", \"baz\" : null}")).asMap());
+ }
+
+ private static void compareValueTypes(Map expected, Map actual) {
+ assertEquals(expected.size(), actual.size());
+ for (var entry : expected.entrySet()) {
+ assertEquals(entry.getValue().getClass(), actual.get(entry.getKey()).getClass());
+ }
+ }
+
+ @Test
+ void immutabilityTest() {
+ var map = new HashMap();
+ map.put("foo", JsonString.of("foo"));
+ var jo = JsonObject.of(map);
+ assertEquals(1, jo.asMap().size());
+ // Modifications to original backed map should not change JsonObject
+ map.put("bar", JsonString.of("foo"));
+ assertEquals(1, jo.asMap().size());
+ // Modifications to JsonObject asMap() should not be possible
+ assertThrows(UnsupportedOperationException.class,
+ () -> jo.asMap().put("bar", JsonNull.of()),
+ "Object members able to be modified");
+ }
+
+ @Test
+ void orderingOfTest() {
+ var jsonFromOf = ((JsonArray)Json.parse(JSON_WITH_SPACES)).asList();
+ assertEquals(JSON_NO_NEWLINE, JsonArray.of(jsonFromOf).toString());
+ }
+
+ @Test
+ void nullTest() {
+ // null map to of factory
+ assertThrows(NullPointerException.class, () -> JsonObject.of(null));
+ Map map = new HashMap<>();
+ // Check null key
+ map.put(null, JsonNull.of());
+ assertThrows(NullPointerException.class, () -> JsonObject.of(map));
+ map.clear();
+ // Check null value
+ map.put("foo", null);
+ assertThrows(NullPointerException.class, () -> JsonObject.of(map));
+ }
+
+ // Ensure decoded escape sequences are translated to valid JSON
+ // Supported 2 char escapes should be translated, otherwise U sequence
+ // needs to be preserved.
+ @Test
+ void controlCodeRoundTripTest() {
+ for (int i = 0; i < 32; i++) {
+ var sequence = Map.of("\\u" + String.format("%04x", i), JsonNull.of());
+ var jo = JsonObject.of(sequence).asMap();
+ JsonObject.of(jo);
+ }
+ }
+
+ // Check IAE is thrown for duplicate map key names
+ @Test
+ void duplicateMapKeyTest() {
+ var map = new IdentityHashMap();
+ map.put(new String("foo"), JsonString.of("foo"));
+ map.put(new String("foo"), JsonString.of("bar"));
+ var iae = assertThrows(IllegalArgumentException.class, () -> JsonObject.of(map));
+ assertEquals("Duplicate member name: foo", iae.getMessage());
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonString.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonString.java
new file mode 100644
index 00000000..c5d0281b
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonString.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import java.util.Arrays;
+import java.util.List;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestJsonString extends JsonTestLoggingConfig {
+
+ @Nested
+ class TestValue {
+
+ @Test
+ void valueTest() {
+ var untypedStr = "\t";
+ var jsonStr = "\"\\u0009\"";
+ // Both should compare as equals via asString() which is \t
+ assertEquals(JsonString.of(untypedStr).asString(), ((JsonString) Json.parse(jsonStr)).asString());
+ // Factory escapes \t to \\t but parse retains the original U sequence
+ // (due to its lazy nature) and that is OK
+ assertNotEquals(JsonString.of(untypedStr).toString(), Json.parse(jsonStr).toString());
+ }
+
+ // Escape sequence tests on asString()
+ @ParameterizedTest
+ @MethodSource
+ void escapeTest(String src, String expected) {
+ assertEquals(expected, ((JsonString)Json.parse(src)).asString());
+ }
+ private static Stream escapeTest() {
+ return Stream.of(
+ Arguments.of("\"\\\"\"", "\""),
+ Arguments.of("\"\\\\\"", "\\"),
+ Arguments.of("\"\\/\"", "/"),
+ Arguments.of("\"\\b\"", "\b"),
+ Arguments.of("\"\\f\"", "\f"),
+ Arguments.of("\"\\n\"", "\n"),
+ Arguments.of("\"\\r\"", "\r"),
+ Arguments.of("\"\\t\"", "\t"),
+ Arguments.of("\"\\uD834\\uDD1E\"", "\uD834\uDD1E")
+ );
+ }
+ }
+
+ @Nested
+ class TestParse {
+
+ // All JsonString related parse failure messages
+ static Stream FAIL_STRING() { return List.of(
+ Arguments.of("\"\t", "Unescaped control code. Path: \"\". Location: line 0, position 1."),
+ Arguments.of("\"foo\\a \"", "Unrecognized escape sequence: \"\\a\". Path: \"\". Location: line 0, position 5."),
+ Arguments.of("\"foo\\\f \"", "Unrecognized escape sequence: \"\\\\u000C\". Path: \"\". Location: line 0, position 5."),
+ Arguments.of("\"foo\\\u0020 \"", "Unrecognized escape sequence: \"\\\\u0020\". Path: \"\". Location: line 0, position 5."),
+ Arguments.of("\"foo\\\u2028 \"", "Unrecognized escape sequence: \"\\\\u2028\". Path: \"\". Location: line 0, position 5."),
+ Arguments.of("\"foo\\u0\"", "Invalid Unicode escape sequence. Expected four hex digits. Path: \"\". Location: line 0, position 5."),
+ Arguments.of("\"foo\\uZZZZ\"", "Invalid Unicode escape sequence. 'Z' is not a hex digit. Path: \"\". Location: line 0, position 6."),
+ Arguments.of("\"foo\\u\f000\"", "Invalid Unicode escape sequence. '\\u000C' is not a hex digit. Path: \"\". Location: line 0, position 6."),
+ Arguments.of("\"foo\\u\u0020000\"", "Invalid Unicode escape sequence. '\\u0020' is not a hex digit. Path: \"\". Location: line 0, position 6."),
+ Arguments.of("\"foo\\u\u2028000\"", "Invalid Unicode escape sequence. '\\u2028' is not a hex digit. Path: \"\". Location: line 0, position 6."),
+ Arguments.of("\"foo ", "JSON String is not closed with a quotation mark. Path: \"\". Location: line 0, position 5.")).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("FAIL_STRING")
+ void testMessages(String json, String err) {
+ Exception e = assertThrows(JsonParseException.class, () -> Json.parse(json));
+ assertEquals(err, e.getMessage());
+ }
+
+ private static Stream testStringEquality() {
+ return Stream.of(
+ Arguments.of("\"afo\"", "\"afo\"", true),
+ Arguments.of("\"afo\"", new char[]{'"', 'a', 'f', 'o', '"'}, true),
+ Arguments.of("\"afo\"", new char[]{'"', '\\', 'u', '0', '0', '6', '1', 'f', 'o', '"'}, true)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testStringEquality(Object arg1, Object arg2) {
+ var jv1 = arg1 instanceof String s ? Json.parse(s) :
+ arg1 instanceof char[] ca ? Json.parse(ca) : null;
+ var jv2 = arg2 instanceof String s ? Json.parse(s) :
+ arg2 instanceof char[] ca ? Json.parse(ca) : null;
+ var val1 = jv1 instanceof JsonString js ? js.asString() : null;
+ var val2 = jv2 instanceof JsonString js ? js.asString() : null;
+
+ // two JsonValue arguments should have the same asString()
+ assertEquals(val1, val2);
+
+ // assert their toString() returns the original text
+ assertEquals(arg1 instanceof char[] ca ? new String(ca) : arg1, jv1.toString());
+ }
+
+ // Ensure decoded escape sequences are translated to valid JSON
+ // Supported 2 char escapes should be translated, otherwise U sequence
+ // needs to be preserved.
+ @Test
+ void controlCodeRoundTripTest() {
+ for (int i = 0; i < 32; i++) {
+ var sequence = "\\u" + String.format("%04x", i);
+ Json.parse(Json.parse("\"" + sequence + "\"").toString());
+ }
+ }
+ }
+
+ @Nested
+ class TestFactory {
+
+ static Stream ESCAPES() { return List.of(
+ // No escape
+ Arguments.of("foo", "\"foo\""),
+ // Escape in front
+ Arguments.of("\" foo", "\"\\\" foo\""),
+ // Escape in back
+ Arguments.of("foo \"", "\"foo \\\"\""),
+ // Various escapes
+ Arguments.of("foo \\\\ \\ \\u0008 \t \b \u0000 \u0001 \u0008"
+ , "\"foo \\\\\\\\ \\\\ \\\\u0008 \\t \\b \\u0000 \\u0001 \\b\"")).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("ESCAPES")
+ void escapeTest(String str, String expected) {
+ assertEquals(expected, JsonString.of(str).toString());
+ }
+
+ // Ensure the String passed to the factory (which requires escaping) can be
+ // round tripped both into a parse call and a factory call.
+ @Test
+ void controlCodeRoundTripTest() {
+ // 0 -> 31 Control chars
+ var reservedChars = Arrays.copyOf(IntStream.range(0, 32).toArray(), 34);
+ reservedChars[32] = 34; // Double quote
+ reservedChars[33] = 92; // Reverse solidus
+ for (int i : reservedChars) {
+ var js = JsonString.of(String.valueOf((char)i));
+ Json.parse(js.toString());
+ JsonString.of(js.asString());
+ }
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestOtherImpl.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestOtherImpl.java
new file mode 100644
index 00000000..45f132aa
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestOtherImpl.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonArray;
+import jdk.incubator.java.util.json.JsonBoolean;
+import jdk.incubator.java.util.json.JsonNull;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TestOtherImpl extends JsonTestLoggingConfig {
+
+ private static final JsonString STANDARD_JSON_STRING = JsonString.of("bar");
+ private static final JsonString ALT_JSON_STRING =
+ new JsonFooString("bar".getBytes(StandardCharsets.UTF_8));
+
+ @Test
+ void equalsToStringTest() {
+ assertEquals(STANDARD_JSON_STRING.toString(), ALT_JSON_STRING.toString());
+ }
+
+ @Test
+ void displayStringTest() {
+ assertEquals(Json.toDisplayString(STANDARD_JSON_STRING, " "), Json.toDisplayString(ALT_JSON_STRING, " "));
+ // Wrap it in a JsonObject, and check display string equality again
+ assertEquals(Json.toDisplayString(JsonObject.of(Map.of("foo", STANDARD_JSON_STRING)), " "),
+ Json.toDisplayString(JsonObject.of(Map.of("foo", ALT_JSON_STRING)), " "));
+ }
+
+ static class JsonFooString implements JsonString {
+
+ private final String theString;
+
+ public JsonFooString(byte[] bytes) {
+ theString = new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public String asString() {
+ // For testing purposes, just return the String.
+ // Real implementations must adhere to un-escaping as specified.
+ return theString;
+ }
+
+ @Override
+ public String toString() {
+ return "\""+theString+"\"";
+ }
+ }
+
+ // These implementation classes exist to verify that each JsonValue
+ // sub-interface is non-sealed. No test execution is required;
+ // successful compilation is sufficient.
+ static class JsonFooArray implements JsonArray {
+ @Override
+ public List asList() {
+ return List.of();
+ }
+ }
+ static class JsonFooBoolean implements JsonBoolean {
+ @Override
+ public boolean asBoolean() {
+ return false;
+ }
+ }
+ static class JsonFooNull implements JsonNull {}
+ static class JsonFooNumber implements JsonNumber {
+ @Override
+ public int asInt() {
+ return 0;
+ }
+ @Override
+ public long asLong() {
+ return 0;
+ }
+ @Override
+ public double asDouble() {
+ return 0;
+ }
+ }
+ static class JsonFooObject implements JsonObject {
+ @Override
+ public Map asMap() {
+ return Map.of();
+ }
+ }
+}
+
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestParse.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestParse.java
new file mode 100644
index 00000000..b74c2eac
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestParse.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.java.util.json.Json;
+import jdk.incubator.java.util.json.JsonNumber;
+import jdk.incubator.java.util.json.JsonObject;
+import jdk.incubator.java.util.json.JsonParseException;
+import jdk.incubator.java.util.json.JsonString;
+import jdk.incubator.java.util.json.JsonValue;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestParse extends JsonTestLoggingConfig {
+
+ private static final String JSON =
+ """
+ { "name": "Brian", "shoeSize": 10 }
+ """;
+
+ // A basic parse and match example
+ @Test
+ void testBasicParseAndMatch() {
+ var doc = Json.parse(JSON);
+ if (doc instanceof JsonObject o && o.asMap() instanceof Map members
+ && members.get("name") instanceof JsonString js
+ && members.get("shoeSize") instanceof JsonNumber jn) {
+ assertEquals("Brian", js.asString());
+ assertEquals(10, jn.asLong());
+ } else {
+ throw new RuntimeException("Test data incorrect");
+ }
+ }
+
+ // Ensure modifying input char array passed to Json.parse has no impact on JsonValue
+ @Test
+ void testDefensiveCopy() {
+ char[] in = JSON.toCharArray();
+ var doc = Json.parse(in);
+
+ // Mutate original char array with nonsense
+ Arrays.fill(in, 'A');
+
+ if (doc instanceof JsonObject o
+ && o.asMap().get("name") instanceof JsonString js
+ && o.asMap().get("shoeSize") instanceof JsonNumber jn) {
+ assertEquals("Brian", js.asString());
+ assertEquals(10, jn.asLong());
+ } else {
+ throw new RuntimeException("JsonValue corrupted by input array");
+ }
+ }
+
+ private static final String JSON_WITH_SPACES =
+ """
+ [
+ { "name": "John", "age": 30, "city": "New York" },
+ { "name": "Jane", "age": 20, "city": "Boston" },
+ true,
+ false,
+ null,
+ [ "array", "inside", {"inner obj": true, "top-level": false}],
+ "foo",
+ 42
+ ]
+ """;
+
+ private static final String JSON_EXTRA_SPACES =
+ """
+ [
+ \s
+ { "name" : "John", "age" : 30, "city": "New York" },
+ { "name": "Jane" , "age": 20, "city": "Boston" },
+ \s
+ \s
+ true, \s
+ false ,
+ null, \s
+ [ "array" , "inside", {"inner obj": true, "top-level" : false } ] ,\s
+ "foo",\s
+ 42
+ ]
+ \s""";
+
+ // White space is allowed but should have no effect
+ // on the underlying structure, and should not play a role during equality
+ @Test
+ void testWhiteSpaceEquality() {
+ var obj = Json.parse(JSON_EXTRA_SPACES);
+ var str = assertDoesNotThrow(obj::toString);
+ var expStr = Json.parse(JSON_WITH_SPACES).toString();
+ // Ensure equivalent Json (besides white space) generates equivalent
+ // toString values
+ assertEquals(expStr, str);
+ }
+
+ @Nested
+ class TestExceptions {
+
+ // General exceptions not particularly tied to a sub-interface of JsonValue
+ static Stream INVALID_JSON() { return List.of(
+ Arguments.of("", "Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"\". Location: line 0, position 0."),
+ Arguments.of(" ", "Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"\". Location: line 0, position 1."),
+ Arguments.of("z", "Unexpected value. Expected a JSON Object, Array, String, Number, Boolean, or Null. Path: \"\". Location: line 0, position 0."),
+ Arguments.of("null, true", "Additional value(s) were found after the JSON Value. Path: \"\". Location: line 0, position 4."),
+ Arguments.of("null 5", "Additional value(s) were found after the JSON Value. Path: \"\". Location: line 0, position 5."),
+ // Test cases focused on path -----------
+ // Compare this case to the one below
+ Arguments.of("{\"foo\": \"bar\"baz}",
+ "Unexpected content after JSON value. Path: \"{foo\". Location: line 0, position 13."),
+ // Notice how this case has a space in between the bar and baz.
+ // This is more an invalid structure, rather than the value being incorrect.
+ // In this case the error is attributed to the structure, and not the value.
+ // This is tricky, since paths in parsing cannot be attributed to valid values and are
+ // contextual/best guesses.
+ Arguments.of("{\"foo\": \"bar\" baz }",
+ "JSON Object is not closed with a brace. Path: \"{\". Location: line 0, position 14."),
+ Arguments.of("[1]x",
+ "Unexpected content after JSON value. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("[1] x",
+ "Additional value(s) were found after the JSON Value. Path: \"\". Location: line 0, position 4."),
+ Arguments.of("[5x ]",
+ "Unexpected content after JSON value. Path: \"[0\". Location: line 0, position 2."),
+ Arguments.of("{} {}",
+ "Additional value(s) were found after the JSON Value. Path: \"\". Location: line 0, position 3."),
+ Arguments.of("[[] 1]",
+ "JSON Array is not closed with a bracket. Path: \"[\". Location: line 0, position 4.")
+ ).stream(); }
+
+ @ParameterizedTest
+ @MethodSource("INVALID_JSON")
+ void testMessages(String json, String err) {
+ Exception e = assertThrows(JsonParseException.class, () -> Json.parse(json));
+ assertEquals(err, e.getMessage());
+ }
+
+
+ // Line Position focused exceptions
+
+ private static final String BASIC = "foobarbaz";
+
+ @Test
+ void testBasicLinePosition() {
+ var msg = "Location: line 0, position 1.";
+ JsonParseException e = assertThrows(JsonParseException.class, () -> Json.parse(BASIC));
+ assertTrue(e.getMessage().contains(msg),
+ "Expected: " + msg + " but got line "
+ + e.getErrorLine() + ", position " + e.getErrorPosition());
+ }
+
+ private static final String STRUCTURAL =
+ """
+ [
+ null, foobarbaz
+ ]
+ """;
+
+ @Test
+ void testStructuralLinePosition() {
+ var msg = "Location: line 1, position 11.";
+ JsonParseException e = assertThrows(JsonParseException.class, () -> Json.parse(STRUCTURAL));
+ assertTrue(e.getMessage().contains(msg),
+ "Expected: " + msg + " but got line "
+ + e.getErrorLine() + ", position " + e.getErrorPosition());
+ }
+
+ private static final String STRUCTURAL_WITH_NESTED =
+ """
+ {
+ "name" :
+ [
+ "value",
+ null, foobarbaz
+ ]
+ }
+ """;
+
+ @Test
+ void testStructuralWithNestedLinePosition() {
+ var msg = "Location: line 4, position 15.";
+ JsonParseException e = assertThrows(JsonParseException.class, () -> Json.parse(STRUCTURAL_WITH_NESTED));
+ assertTrue(e.getMessage().contains(msg),
+ "Expected: " + msg + " but got line "
+ + e.getErrorLine() + ", position " + e.getErrorPosition());
+ }
+
+ @Test
+ void testConstructorIAE() {
+ assertThrows(IllegalArgumentException.class, () -> new JsonParseException("Foo", 1, -1));
+ assertThrows(IllegalArgumentException.class, () -> new JsonParseException("Foo", -1, 1));
+ }
+ }
+
+ @Test
+ void testDeepNestingParse() {
+ int depth = 10_000;
+ var json = "[".repeat(depth) + "0" + "]".repeat(depth);
+ var parsed = assertDoesNotThrow(() -> Json.parse(json));
+ assertEquals(json, parsed.toString());
+ }
+}
From 2155903143a0bcc06d204f8c82690c5a1439775c Mon Sep 17 00:00:00 2001
From: Simon Massey <322608+simbo1905@users.noreply.github.com>
Date: Sun, 30 Aug 2026 08:06:05 +0100
Subject: [PATCH 8/9] Issue #145 harden JSON number math test coverage
Audit of the full JSON number-math input space after the 43325738c uplift
found the ported upstream tests rigorous except for boundary extremes:
Integer.MIN_VALUE / Long.MIN_VALUE as accepted conversions, exponents at
the int boundaries (1e2147483647 / 1e2147483648 / 1e-2147483648 /
1e-2147483649) that exercise Math.addExact/subtractExact/negateExact and
the Utils.powExact polyfill, parsed -0 value semantics, the
JsonNumber.of(String) whitespace contract, and Json.parse("-")/parse("").
New JsonNumberBoundaryTest (10 tests) covers these; expected behaviour is
sourced from the JsonValue javadoc ranges, the upstream implementation
and RFC 8259 section 6. TestJsonNumberOfDouble now extends
JsonTestLoggingConfig and drops its ad-hoc System.out per module test
rules. Full suite 1665 -> 1675; ci.yml exp_tests updated.
How to verify:
/opt/homebrew/bin/mvnd -pl json-java21 test -Djava.util.logging.ConsoleHandler.level=INFO
/opt/homebrew/bin/mvnd clean verify -Djava.util.logging.ConsoleHandler.level=INFO (all modules green)
---
.github/workflows/ci.yml | 2 +-
.../util/json/JsonNumberBoundaryTest.java | 128 ++++++++++++++++++
.../util/json/TestJsonNumberOfDouble.java | 9 +-
3 files changed, 133 insertions(+), 6 deletions(-)
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberBoundaryTest.java
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index de0213b9..1bc3ed38 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
- exp_tests=1665
+ exp_tests=1675
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}")
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberBoundaryTest.java b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberBoundaryTest.java
new file mode 100644
index 00000000..904b54c7
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/JsonNumberBoundaryTest.java
@@ -0,0 +1,128 @@
+package jdk.incubator.java.util.json;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/// Boundary hardening for JSON number math, added by the final-defense audit
+/// of issue #145. Complements the ported upstream {@code TestJsonNumber} and
+/// the issue #118 {@code JsonNumberOfDoubleMatrixTest} with the extremes that
+/// exercise {@code JsonNumberImpl}'s fast path, overflow guards
+/// ({@code Math.addExact}/{@code subtractExact}/{@code negateExact} and
+/// {@code Utils.powExact}) and the {@code JsonNumber.of(String)} whitespace
+/// contract. Expected behaviour is taken from the {@link JsonValue} javadoc
+/// ranges ("MIN_VALUE to MAX_VALUE, inclusive"), the upstream implementation
+/// and RFC 8259 section 6.
+public class JsonNumberBoundaryTest extends JsonTestLoggingConfig {
+
+ @Test
+ void integerMinValueBoundaryIsRepresentable() {
+ var jn = Json.parse("-2147483648");
+ assertEquals(Integer.MIN_VALUE, jn.asInt(), "asInt at Integer.MIN_VALUE");
+ assertEquals(-2147483648L, jn.asLong(), "asLong at Integer.MIN_VALUE");
+ assertEquals(-2147483648.0d, jn.asDouble(), "asDouble at Integer.MIN_VALUE");
+ assertEquals("-2147483648", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void longMinValueBoundaryIsRepresentable() {
+ var jn = Json.parse("-9223372036854775808");
+ assertEquals(Long.MIN_VALUE, jn.asLong(), "asLong at Long.MIN_VALUE");
+ assertThrows(JsonValueException.class, jn::asInt, "asInt beyond Integer range");
+ assertEquals(-9.223372036854776E18d, jn.asDouble(), "asDouble at Long.MIN_VALUE");
+ assertEquals("-9223372036854775808", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void exponentAtIntMaxOverflowsAllConversions() {
+ // 1e2147483647: Integer.parseInt of the exponent succeeds, but
+ // Utils.powExact(10, 2147483647) overflows long -> ArithmeticException
+ // -> Optional.empty -> JsonValueException; asDouble is Infinity.
+ var jn = Json.parse("1e2147483647");
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for 1e2147483647");
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for 1e2147483647");
+ assertThrows(JsonValueException.class, jn::asDouble, "asDouble for 1e2147483647");
+ assertEquals("1e2147483647", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void exponentAboveIntMaxOverflowsAllConversions() {
+ // 1e2147483648: Integer.parseInt of the exponent itself overflows.
+ var jn = Json.parse("1e2147483648");
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for 1e2147483648");
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for 1e2147483648");
+ assertThrows(JsonValueException.class, jn::asDouble, "asDouble for 1e2147483648");
+ assertEquals("1e2147483648", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void exponentAtIntMinOverflowsIntegralOnly() {
+ // 1e-2147483648: Math.negateExact(Integer.MIN_VALUE) overflows during
+ // the 10^power division path -> JsonValueException for asInt/asLong;
+ // asDouble underflows to a finite 0.0 via Double.parseDouble.
+ var jn = Json.parse("1e-2147483648");
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for 1e-2147483648");
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for 1e-2147483648");
+ assertEquals(0.0d, jn.asDouble(), "asDouble for 1e-2147483648");
+ assertEquals("1e-2147483648", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void exponentBelowIntMinOverflowsIntegralOnly() {
+ // 1e-2147483649: Integer.parseInt of the exponent fails; asDouble
+ // still underflows to a finite 0.0.
+ var jn = Json.parse("1e-2147483649");
+ assertThrows(JsonValueException.class, jn::asInt, "asInt for 1e-2147483649");
+ assertThrows(JsonValueException.class, jn::asLong, "asLong for 1e-2147483649");
+ assertEquals(0.0d, jn.asDouble(), "asDouble for 1e-2147483649");
+ assertEquals("1e-2147483649", jn.toString(), "toString preservation");
+ }
+
+ @Test
+ void parsedNegativeZeroKeepsTextAndValue() {
+ var jn = Json.parse("-0");
+ assertEquals("-0", jn.toString(), "toString preservation");
+ assertEquals(0, jn.asInt(), "asInt of -0");
+ assertEquals(0L, jn.asLong(), "asLong of -0");
+ // Double.compare distinguishes -0.0 from 0.0; RFC 8259 permits "-0"
+ // and asDouble is specified to properly return negative zero.
+ assertEquals(0, Double.compare(jn.asDouble(), -0.0d), "asDouble of -0 is negative zero");
+ }
+
+ @Test
+ void ofStringStripsInsignificantWhitespace() {
+ // JsonNumber.of(String) javadoc: the representation is equivalent to
+ // num with any leading or trailing JSON insignificant whitespaces removed.
+ var jn = JsonNumber.of(" 3 ");
+ assertEquals("3", jn.toString(), "toString of of(\" 3 \")");
+ assertEquals(3, jn.asInt(), "asInt of of(\" 3 \")");
+ var neg = JsonNumber.of("\t-0.5\n");
+ assertEquals("-0.5", neg.toString(), "toString of of(\"\\t-0.5\\n\")");
+ assertEquals(-0.5d, neg.asDouble(), "asDouble of of(\"\\t-0.5\\n\")");
+ }
+
+ @Test
+ void bareMinusAndEmptyTextAreNotJsonNumbers() {
+ // RFC 8259 section 6: a number must contain at least one integer digit;
+ // an empty text is not a JSON value (RFC 8259 section 2).
+ assertThrows(JsonParseException.class, () -> Json.parse("-"), "parse(\"-\")");
+ assertThrows(JsonParseException.class, () -> Json.parse(""), "parse(\"\")");
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of("-"), "of(\"-\")");
+ assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(""), "of(\"\")");
+ }
+
+ @Test
+ void intMinValueViaFactoryStringContract() {
+ // of(String) must accept the same boundary text parse accepts and be
+ // behaviourally identical to the parsed value.
+ var viaFactory = JsonNumber.of("-2147483648");
+ var viaParse = Json.parse("-2147483648");
+ assertEquals(viaParse.toString(), viaFactory.toString(), "toString parity");
+ assertEquals(Integer.MIN_VALUE, viaFactory.asInt(), "asInt parity");
+ assertEquals(Long.MIN_VALUE, JsonNumber.of("-9223372036854775808").asLong(),
+ "asLong of of(\"-9223372036854775808\")");
+ assertTrue(viaFactory instanceof JsonNumber, "factory produces JsonNumber");
+ }
+}
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
index 18209404..a78a3f0c 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonNumberOfDouble.java
@@ -3,25 +3,24 @@
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
-public class TestJsonNumberOfDouble {
-
+public class TestJsonNumberOfDouble extends JsonTestLoggingConfig {
+
@Test
void ofDoubleToStringPreservesValue() {
var jn = JsonNumber.of(123.45);
assertThat(jn.toString()).isEqualTo("123.45");
}
-
+
@Test
void ofDoubleToDoubleWorks() {
var jn = JsonNumber.of(123.45);
assertThat(jn.asDouble()).isEqualTo(123.45);
}
-
+
@Test
void ofDoubleThenToLongForIntegralDouble() {
// 123.0 should be convertible to long 123
var jn = JsonNumber.of(123.0);
- System.out.println("toString: " + jn.toString());
assertThat(jn.asLong()).isEqualTo(123L);
}
From c3fdf4e187ce94db9af8945d1a7182db69d3c365 Mon Sep 17 00:00:00 2001
From: Simon Massey <322608+simbo1905@users.noreply.github.com>
Date: Sun, 30 Aug 2026 08:23:16 +0100
Subject: [PATCH 9/9] Issue #145 remove dead code and stale docs from backport
- Delete unannotated dead TestJsonLiteral.conversionTest() (upstream-faithful;
assertions already covered by live booleanOfTest())
- Wire ReadmeExamples into ReadmeExamplesTest so the README.md and index.html
promise of a runnable examples class is test-enforced (exp_tests 1675 -> 1676)
- ApiTracker: delete unreachable NOT_IMPLEMENTED/PARSE_NOT_IMPLEMENTED status
path (nothing produces that status) and fix discoverLocalJsonClasses javadoc
to match the actual packages list
- ApiTrackerRunner: delete parsed-but-ignored binary|source mode and sourcepath
args; both workflows invoke with only the log level argument
- LazyConstant: drop stale comment referencing the removed StableValue polyfill
- Remove 15 unused imports across jtd, jtd-codegen, jsonpath and compatibility
suite sources
- Delete 8 stray pom.xml.versionsBackup files (untracked; .gitignore already
covers the pattern)
Verify: mvnd clean verify green; surefire totals tests=1676 failures=0
errors=0 skipped=0.
---
.github/workflows/ci.yml | 2 +-
.../DownloadVerificationTest.java | 1 -
.../github/simbo1905/tracker/ApiTracker.java | 11 +---
.../simbo1905/tracker/ApiTrackerRunner.java | 12 +---
.../java21/jsonpath/JsonPathParserTest.java | 2 -
.../java21/jtd/codegen/EmitDiscriminator.java | 1 -
.../json/java21/jtd/codegen/EmitNode.java | 1 -
.../json/java21/jtd/codegen/EmitType.java | 1 -
.../json/java21/jtd/codegen/JtdCodegen.java | 2 -
.../java21/jtd/JtdSpecConformanceTest.java | 1 -
.../json/java21/jtd/JtdValidatorTest.java | 2 -
.../java21/jtd/NullableEdgeCaseProbe.java | 1 -
.../json/java21/jtd/RefEdgeCaseProbe.java | 2 -
.../jtd/TypeValidationEdgeCaseProbe.java | 1 -
.../internal/util/json/LazyConstant.java | 2 +-
.../java/util/json/TestJsonLiteral.java | 6 --
.../json/examples/ReadmeExamplesTest.java | 64 +++++++++++++++++++
17 files changed, 68 insertions(+), 44 deletions(-)
create mode 100644 json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamplesTest.java
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1bc3ed38..da35e7c1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
- exp_tests=1675
+ exp_tests=1676
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}")
diff --git a/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java b/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
index 96b05557..704635c7 100644
--- a/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
+++ b/json-compatibility-suite/src/test/java/jdk/incubator/compatibility/DownloadVerificationTest.java
@@ -1,7 +1,6 @@
package jdk.incubator.compatibility;
import org.junit.jupiter.api.Test;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
index fb653dc6..0266f08f 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTracker.java
@@ -97,7 +97,7 @@ static String fetchFromUrl(String url) {
}
/// Discovers all classes in the local JSON API packages
- /// @return sorted set of classes from jdk.incubator.java.util.json and jdk.incubator.internal.util.json
+ /// @return sorted set of classes from jdk.incubator.java.util.json
static Set> discoverLocalJsonClasses() {
LOGGER.info("Starting class discovery for JSON API packages");
final var classes = new TreeSet>(Comparator.comparing(Class::getName));
@@ -610,15 +610,6 @@ static JsonObject compareApis(JsonObject local, JsonObject upstream) {
return JsonObject.of(diffMap);
}
- // Check if status is NOT_IMPLEMENTED (from parsing)
- if (upstream.asMap().containsKey("status")) {
- final var status = ((JsonString) upstream.asMap().get("status")).asString();
- if ("NOT_IMPLEMENTED".equals(status)) {
- diffMap.put("status", JsonString.of("PARSE_NOT_IMPLEMENTED"));
- return JsonObject.of(diffMap);
- }
- }
-
// Perform detailed comparison
final var differences = new ArrayList();
var hasChanges = false;
diff --git a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
index bdb4f947..6ff33eb4 100644
--- a/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
+++ b/json-java21-api-tracker/src/main/java/io/github/simbo1905/tracker/ApiTrackerRunner.java
@@ -11,32 +11,22 @@
/// Command-line runner for the API Tracker
///
-/// Usage: java io.github.simbo1905.tracker.ApiTrackerRunner [loglevel] [mode] [sourcepath]
+/// Usage: java io.github.simbo1905.tracker.ApiTrackerRunner [loglevel]
///
/// Arguments:
/// - loglevel: SEVERE, WARNING, INFO, FINE, FINER, FINEST (default: INFO)
-/// - mode: binary|source (default: binary)
-/// - binary: Compare binary reflection (local) vs source parsing (remote)
-/// - source: Compare source parsing (local) vs source parsing (remote) for accurate parameter names
-/// - sourcepath: Path to local source files (required for source mode)
@SuppressWarnings("JavadocReference")
public class ApiTrackerRunner {
public static void main(String[] args) {
// Parse command line arguments
final var logLevel = args.length > 0 ? Level.parse(args[0].toUpperCase()) : Level.INFO;
- final var mode = args.length > 1 ? args[1].toLowerCase() : "binary";
- final var sourcePath = args.length > 2 ? args[2] : null;
configureLogging(logLevel);
System.out.println("=== JSON API Tracker ===");
System.out.println("Comparing local jdk.incubator.java.util.json with upstream jdk.incubator.json");
System.out.println("Log level: " + logLevel);
- System.out.println("Mode: " + mode);
- if (sourcePath != null) {
- System.out.println("Local source path: " + sourcePath);
- }
System.out.println();
try {
diff --git a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
index d645b10b..3a08a10c 100644
--- a/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
+++ b/json-java21-jsonpath/src/test/java/json/java21/jsonpath/JsonPathParserTest.java
@@ -1,7 +1,5 @@
package json.java21.jsonpath;
-import jdk.incubator.java.util.json.Json;
-import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
index 1e546c9f..dee3a4a6 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitDiscriminator.java
@@ -2,7 +2,6 @@
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.TypeKind;
-import java.lang.constant.ConstantDescs;
import json.java21.jtd.JtdSchema;
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitNode.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitNode.java
index ca8da615..76aee8d3 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitNode.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitNode.java
@@ -2,7 +2,6 @@
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.TypeKind;
-import java.lang.constant.ConstantDescs;
import java.util.logging.Logger;
import json.java21.jtd.JtdSchema;
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
index 26c95929..25b31761 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/EmitType.java
@@ -2,7 +2,6 @@
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.TypeKind;
-import java.lang.constant.ConstantDescs;
import static json.java21.jtd.codegen.Descriptors.*;
diff --git a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
index 609432d9..fa0a4209 100644
--- a/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
+++ b/json-java21-jtd-codegen/src/main/java/json/java21/jtd/codegen/JtdCodegen.java
@@ -3,8 +3,6 @@
import java.lang.classfile.*;
import java.lang.classfile.attribute.SourceFileAttribute;
import java.lang.constant.ClassDesc;
-import java.lang.constant.ConstantDescs;
-import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
index a207b039..91133278 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdSpecConformanceTest.java
@@ -12,7 +12,6 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
-import java.util.List;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java b/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
index e669f58f..3f7716ad 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/JtdValidatorTest.java
@@ -1,13 +1,11 @@
package json.java21.jtd;
import jdk.incubator.java.util.json.Json;
-import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
/// Tests for the [JtdValidator] functional interface and [InterpreterValidator].
///
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
index e23dae60..737d721b 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/NullableEdgeCaseProbe.java
@@ -4,7 +4,6 @@
import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
/// Probes for Nullable modifier edge cases and potential issues
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
index 46e9fc48..a41e7797 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/RefEdgeCaseProbe.java
@@ -4,9 +4,7 @@
import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
-import java.util.List;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
/// Probes for Ref schema edge cases and potential issues
diff --git a/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java b/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
index 1f0e24cc..046ad777 100644
--- a/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
+++ b/json-java21-jtd/src/test/java/json/java21/jtd/TypeValidationEdgeCaseProbe.java
@@ -4,7 +4,6 @@
import jdk.incubator.java.util.json.JsonValue;
import org.junit.jupiter.api.Test;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
/// Probes for Type validation edge cases and potential issues
diff --git a/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
index a31b9e0f..0c0519fe 100644
--- a/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
+++ b/json-java21/src/main/java/jdk/incubator/internal/util/json/LazyConstant.java
@@ -5,7 +5,7 @@
/// Polyfill for JDK's LazyConstant using double-checked locking pattern
/// for thread-safe lazy initialization.
///
-/// This provides a simpler API than the legacy StableValue:
+/// API:
/// - `LazyConstant.of(Supplier)` - creates a lazy constant
/// - `.get()` - gets the value (computing if needed)
class LazyConstant {
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
index a543578a..b298ec2e 100644
--- a/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/TestJsonLiteral.java
@@ -40,18 +40,12 @@
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestJsonLiteral extends JsonTestLoggingConfig {
- void conversionTest() {
- assertTrue(JsonBoolean.of(true).asBoolean());
- assertFalse(JsonBoolean.of(false).asBoolean());
- }
-
@Nested
class TestParse {
diff --git a/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamplesTest.java b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamplesTest.java
new file mode 100644
index 00000000..ea44057a
--- /dev/null
+++ b/json-java21/src/test/java/jdk/incubator/java/util/json/examples/ReadmeExamplesTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.java.util.json.examples;
+
+import jdk.incubator.java.util.json.JsonTestLoggingConfig;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/// The README.md ("Running the Examples") and index.html both promise
+/// `jdk.incubator.java.util.json.examples.ReadmeExamples` as a runnable
+/// artifact. This test keeps that promise honest by executing the examples
+/// and verifying they run to completion without throwing.
+public class ReadmeExamplesTest extends JsonTestLoggingConfig {
+
+ private static final Logger LOG = Logger.getLogger(ReadmeExamplesTest.class.getName());
+
+ @Test
+ void readmeExamplesRunToCompletion() {
+ final var out = new ByteArrayOutputStream();
+ final var originalOut = System.out;
+ System.setOut(new PrintStream(out, true, StandardCharsets.UTF_8));
+ try {
+ assertDoesNotThrow(() -> ReadmeExamples.main(new String[0]));
+ } finally {
+ System.setOut(originalOut);
+ }
+ final var output = out.toString(StandardCharsets.UTF_8);
+ LOG.info(() -> "ReadmeExamples produced " + output.length() + " chars of output");
+ assertTrue(output.contains("All examples completed successfully!"),
+ "ReadmeExamples did not report successful completion; output was:\n" + output);
+ assertTrue(output.contains("1. Quick Start Example"),
+ "ReadmeExamples did not run the first example; output was:\n" + output);
+ }
+}