On Fri, 14 Aug 2026 18:19:46 GMT, Naoto Sato <[email protected]> wrote:

>> This PR implements [JEP 540: Simple JSON API 
>> (Incubator)](https://openjdk.org/jeps/540).
>> 
>> It adds the `jdk.incubator.json` module which provides APIs for reading and 
>> writing JSON documents as specified by [RFC 
>> 8259](https://datatracker.ietf.org/doc/html/rfc8259). This is an incubating 
>> API.
>> 
>> API documentation: 
>> https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/module-summary.html
>> Co-authored-by: Justin Lu 
>> ([@justin-curtis-lu](https://github.com/justin-curtis-lu))
>> 
>> ---------
>> - [x] I confirm that I make this contribution in accordance with the 
>> [OpenJDK Interim AI Policy](https://openjdk.org/legal/ai).
>
> Naoto Sato has updated the pull request with a new target base due to a merge 
> or a rebase. The pull request now contains 726 commits:
> 
>  - Merge remote-tracking branch 'jdk-sandbox/json' into 
> JDK-8381976-Implementation-for-Simple-JSON-API
>  - Address comment regarding asDouble wording in class spec
>  - Reflects Alan's comments
>  - Merge branch 'master' into JDK-8381976-Implementation-for-Simple-JSON-API
>  - Merge remote-tracking branch 'jdk-sandbox/json' into 
> JDK-8381976-Implementation-for-Simple-JSON-API
>  - Removed unused imports
>  - Removed unnecessary qualifiers
>  - Merge remote-tracking branch 'jdk-sandbox/json' into 
> JDK-8381976-Implementation-for-Simple-JSON-API
>  - Merge branch 'toDisplayString' into json
>  - wording
>  - ... and 716 more: https://git.openjdk.org/jdk/compare/16bf2730...76272098

The API of this looks quite nice; minimal but pretty useful!
Thanks for all the work and time you have put into this! I still remember when 
you had published your first draft a few months ago.

I am not an OpenJDK member, but hopefully these comments are useful 
nonetheless. Feel free to consider them only as suggestions.
(I only had a look at the non-test sources though.)

Note also that because I am not a member, I cannot mark my own comments as 
resolved (if I recall correctly), so feel free to resolve them in case you have 
considered them or don't think they are relevant.

src/jdk.incubator.json/share/classes/jdk/incubator/json/Json.java line 102:

> 100:         Objects.requireNonNull(in);
> 101:         // Defensive copy on input. Ensure source is immutable.
> 102:         return new JsonParser(Arrays.copyOf(in, in.length)).parseRoot();

Can simplify this

Suggestion:

        return new JsonParser(in.clone()).parseRoot();

src/jdk.incubator.json/share/classes/jdk/incubator/json/Json.java line 146:

> 144:         var prefix = indent.repeat(depth);
> 145:         if (isField) {
> 146:             s.append(" ");

Here and below use `append(char)` where possible?

src/jdk.incubator.json/share/classes/jdk/incubator/json/Json.java line 163:

> 161:                 s.append(",\n");
> 162:             });
> 163:             s.setLength(s.length() - 2); // trim final comma

For consistency with comment in `toDisplayString(JsonArray, ...)`
Suggestion:

            s.setLength(s.length() - 2); // trim final comma/newline

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonArray.java line 56:

> 54:      * @throws JsonValueException if the given index is out of bounds
> 55:      */
> 56:     default JsonValue get(int index) {

Missing `@Override`?

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonNumber.java line 39:

> 37:  * <a href="https://datatracker.ietf.org/doc/html/rfc8259#section-6";>
> 38:  * syntax</a>.
> 39:  * Alternatively, {@link #of(int)}, {@link #of(long)}, {@link 
> #of(double)},

For consistency with the Javadoc of the other interfaces, have a separate 
paragraph for this?

Suggestion:

 * <p> Alternatively, {@link #of(int)}, {@link #of(long)}, {@link #of(double)},

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonNumber.java line 
131:

> 129:         }
> 130:         var str = Double.toString(num);
> 131:         return new JsonNumberImpl(str.toCharArray(), 0, str.length(), 
> str.indexOf('.'), str.indexOf('E'));

Will using the string representation here and treating it as `doc` lead to 
confusing error messages? For example `JsonNumber.of(1.5).asLong()` will report 
a confusing JSON document location?

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonNumber.java line 
142:

> 140:      * @return a {@code JsonNumber} created from the {@code int} value
> 141:      */
> 142:     static JsonNumber of(int num) {

For these methods, would it make sense to have `JsonNumberImpl` constructors 
taking the `long` (including `int` cast to `long`) and `double` value, to avoid 
having to redundantly reparse them?

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonObject.java line 39:

> 37:  * The interface that represents JSON object.
> 38:  * <p>
> 39:  * A {@code JsonObject} can be produced by a {@link Json#parse(String)}.

Suggestion:

 * A {@code JsonObject} can be produced by {@link Json#parse(String)}.

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonString.java line 34:

> 32: 
> 33: /**
> 34:  * The interface that represents a JSON string.

For consistency with the Javadoc of all the other interfaces:
Suggestion:

 * The interface that represents JSON string.


(respectively should the other ones without canonical value (`true`, `false` 
and `null`) have an article in the first sentence of their Javadoc?)

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonString.java line 36:

> 34:  * The interface that represents a JSON string.
> 35:  * <p>
> 36:  * A {@code JsonString} can be produced by a {@link Json#parse(String)}.

Suggestion:

 * A {@code JsonString} can be produced by {@link Json#parse(String)}.

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonString.java line 65:

> 63:      * @throws NullPointerException if {@code src} is {@code null}
> 64:      */
> 65:     static JsonString of(String src) {

To avoid any confusion, should `of(String)` explicitly mention that it expects 
the unescaped value, without enclosing `"`?

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonString.java line 67:

> 65:     static JsonString of(String src) {
> 66:         var escaped = '"' + Utils.escape(Objects.requireNonNull(src)) + 
> '"';
> 67:         return new JsonStringImpl(escaped.toCharArray(), 0, 
> escaped.length(),

Similar to the comment for `JsonNumber#of`; could this lead to misleading 
exception messages because this sets `doc`, and then appears as path in the 
exception message? E.g. `JsonString.of("a").asDouble()`

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonString.java line 69:

> 67:         return new JsonStringImpl(escaped.toCharArray(), 0, 
> escaped.length(),
> 68:                 escaped.length() != src.length() + 2);
> 69:     }

Would it make sense to optimize this, e.g. by having a `JsonStringImpl` 
constructor which takes the unescaped value?

Current behavior might not be ideal that `JsonString.of(...).asString()` can 
involve escaping and unescaping.

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonValue.java line 184:

> 182:      * {@return the {@code boolean} value represented by this {@code 
> JsonValue} if
> 183:      * it is an instance of {@link JsonBoolean}; otherwise, throws a
> 184:      * {@code JsonValueException}}.

Suggestion:

     * {@code JsonValueException}}

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonValue.java line 269:

> 267:      * {@return the {@code String} value represented by this {@code 
> JsonValue} if
> 268:      * it is an instance of {@link JsonString}; otherwise, throws a
> 269:      * {@code JsonValueException}}.

Results in duplicate period

Suggestion:

     * {@code JsonValueException}}

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonValue.java line 288:

> 286:      * {@return an unmodifiable list of the {@code JsonValue}s if this
> 287:      * {@code JsonValue} is an instance of {@link JsonArray}; otherwise, 
> throws a
> 288:      * {@code JsonValueException}}.

Suggestion:

     * {@code JsonValueException}}

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonValue.java line 304:

> 302:      * {@return an unmodifiable map of {@code String} to {@code 
> JsonValue} if this
> 303:      * {@code JsonValue} is an instance of {@link JsonObject}; 
> otherwise, throws a
> 304:      * {@code JsonValueException}}.

Suggestion:

     * {@code JsonValueException}}

src/jdk.incubator.json/share/classes/jdk/incubator/json/JsonValue.java line 388:

> 386:         } catch (IndexOutOfBoundsException _) {
> 387:             throw Utils.composeError(this,
> 388:                     "JsonArray index %d out of bounds for length %d."

Specify `Locale.ENGLISH` for `%d`?

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonArrayImpl.java 
line 67:

> 65:             throw Utils.composeError(this,
> 66:                     "JsonArray index %d out of bounds for length %d."
> 67:                             .formatted(index, theValues.size()));

Should this specify `Locale.ENGLISH`? Otherwise depending on the OS default 
locale you end up with an English exception message containing non-English 
numerals, which might make troubleshooting a bit cumbersome.

(Not sure if other parts of the JDK code account for this though.)

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonArrayImpl.java 
line 91:

> 89:             s.setLength(s.length() - 1); // trim final comma
> 90:         }
> 91:         return s.append("]").toString();

Could use `append(char)` here
Suggestion:

            s.append(v.toString()).append(',');
        }
        if (!list.isEmpty()) {
            s.setLength(s.length() - 1); // trim final comma
        }
        return s.append(']').toString();

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonBooleanImpl.java
 line 2:

> 1: /*
> 2:  * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.

2026 missing?

Same also for `JsonNullImpl.java` and `JsonNull.java`

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonObjectImpl.java
 line 101:

> 99:             s.setLength(s.length() - 1); // trim final comma
> 100:         }
> 101:         return s.append("}").toString();

Use `append(char)`?

Suggestion:

            s.append('"').append(Utils.escape(kv.getKey())).append("":")
             .append(kv.getValue().toString())
             .append(',');
        }
        if (!map.isEmpty()) {
            s.setLength(s.length() - 1); // trim final comma
        }
        return s.append('}').toString();

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 99:

> 97:         if (hasInput()) {
> 98:             switch (doc[offset]) {
> 99:                 case ']', '}', ',', ' ', '\t','\r', '\n' -> {}

Suggestion:

                case ']', '}', ',', ' ', '\t', '\r', '\n' -> {}

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 137:

> 135:             if (members.putIfAbsent(name, parseValue()) != null) {
> 136:                 throw failure(nameStart, nameLine, nameLineStart,
> 137:                     "The duplicate member name: \"%s\" was already 
> parsed".formatted(name), startO, true);

Message sounds maybe a bit weird, should this either omit the leading "The ..." 
or omit the colon ":"?
- "Duplicate member name: ..."
- "The duplicate member name ..."

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 193:

> 191:                 escape = true;
> 192:                 continue;
> 193:             } else if (c == '\"') {

Redundant escape

Suggestion:

            } else if (c == '"') {

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 263:

> 261:                 hasEscape = true;
> 262:                 escape = true;
> 263:             } else if (c == '\"') {

Redundant escape

Suggestion:

            } else if (c == '"') {

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 419:

> 417:     private boolean notWhitespace() {
> 418:         return switch (doc[offset]) {
> 419:             case ' ', '\t','\r' -> false;

Suggestion:

            case ' ', '\t', '\r' -> false;

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/JsonParser.java 
line 459:

> 457:         var path = Utils.getParsingPath(head, doc, structural);
> 458:         return new JsonParseException("%s.%s Location: line %d, position 
> %d."
> 459:                 .formatted(message, path, l, pos), l, pos);

Similar to other comments; use `Locale.ENGLISH` for `%d`?

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 64:

> 62:                 if (sb == null) {
> 63:                     sb = new StringBuilder().append(str, 0, i);
> 64:                 }

Reduce code duplication by performing `sb.append('\')` here once? All the `if` 
branches below duplicate this currently.

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 69:

> 67:                     sb.append('\\').append(c);
> 68:                 } else if (c == '"') {
> 69:                     sb.append('\\').append(c);

Combine these into a single `if`?

Suggestion:

                if (c == '\' || c == '"') {
                    sb.append('\').append(c);

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 
138:

> 136:                 // Structural parsing cases
> 137:                 if (doc[offset] == '[') {
> 138:                     sb.append( '[');

Suggestion:

                    sb.append('[');

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 
140:

> 138:                     sb.append( '[');
> 139:                 }
> 140:                 if (doc[offset] == '{') {

Can be `else if`

Suggestion:

                else if (doc[offset] == '{') {

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 
156:

> 154:             // After path is produced, line and pos should be value 
> bearing
> 155:             return " Path: \"%s\". Location: line %d, position 
> %d.".formatted(
> 156:                     path, jp.line, jp.pos);

Same as other comment; explicitly specify `Locale.ENGLISH` to avoid `%d` being 
OS default locale dependent?

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 
190:

> 188:                     default -> throw new InternalError();
> 189:                 };
> 190:                 toPath(offset, sb);

Instead of implementing this recursively, could this instead be implemented 
iteratively by wrapping the whole body of `toPath` inside a `while (offset > 0) 
{ ... }`?

src/jdk.incubator.json/share/classes/jdk/incubator/json/impl/Utils.java line 
197:

> 195:             while (offset >= 0) {
> 196:                 var ws = switch (doc[offset]) {
> 197:                     case ' ', '\t','\r' -> true;

Suggestion:

                    case ' ', '\t', '\r' -> true;

src/jdk.incubator.json/share/classes/jdk/incubator/json/package-info.java line 
30:

> 28:  * generating JSON text. This package implements
> 29:  * <a href="https://datatracker.ietf.org/doc/html/rfc8259";>RFC 8259: The 
> JavaScript
> 30:  * Object Notation (JSON) Data Interchange Format</a>

Missing period?

Suggestion:

 * Object Notation (JSON) Data Interchange Format</a>.

src/jdk.incubator.json/share/classes/jdk/incubator/json/package-info.java line 
32:

> 30:  * Object Notation (JSON) Data Interchange Format</a>
> 31:  *
> 32:  * <h2><a>Parsing JSON documents</a></h2>

Here and below, why these `<h2><a>`? Isn't Javadoc creating anchors for 
headings on its own? (or what is the intention here?)

-------------

PR Review: https://git.openjdk.org/jdk/pull/32282#pullrequestreview-4947182226
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792878114
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792884186
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792889223
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792908451
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792912918
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792942494
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792936096
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792948219
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3793007828
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792957334
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792967342
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792972908
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792804942
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792997269
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792996337
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792996607
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792996855
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3793014420
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792685376
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792691000
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792924068
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792733866
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792816761
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792838796
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792847493
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792847868
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792865101
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792868658
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792748357
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792745214
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792758396
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792760232
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792762575
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792775207
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3792765185
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3793018581
PR Review Comment: https://git.openjdk.org/jdk/pull/32282#discussion_r3793021990

Reply via email to