I present for your consideration the library I made when spiraling about this problem space a few years ago
https://github.com/bowbahdoe/json https://javadoc.io/doc/dev.mccue/json/latest/dev.mccue.json/dev/mccue/json/package-summary.html Notably missing during the design process here were patterns, hence the JsonDecoder design. I haven't been able to evaluate how patterns affect that on account of them not being out. I will more thoroughly peruse the draft of java.util.json at a later date, but my initial observations/comments: * I am not sure having JsonValue be distinct from Json has value. * toUntyped feels a little strange to me - the only type information presumably lost is the sealed-ness of the hierarchy. The interplay between that and toNumber is also a little unnerving. * One notion that I found helpful was that a class could be "json encodable," meaning there is a method to call to obtain a canonical json representation. record Person(String name) implements JsonEncodable { @Override public Json toJson() { return Json.objectBuilder() .put("namen", name) .build(); } } Which helper methods like Json#of(List<? extends JsonEncodable>) could make use of. Json itself (JsonValue in your prototype) could then have a vacuous implementation. * Terminology wise - I went with reading/writing for the actual parsing/generation of json and encoding/decoding for the mapping of those representations to/from specific classes. The merits are not top of mind, just noting the difference. read/write vs parse/toString+toDisplayString * One thing I did was make the helper methods in Json null tolerant and the ones in the specific subtypes like JsonString not. This was because from what I saw of usages of javax.json/jakarta.json that nullability was a footgun and correcting for it required changes to code structure (breaking up builder chains with if (x != null) checks) * The functionality you want from JsonNumber could be achieved by making it just extend Number ( https://github.com/bowbahdoe/json/blob/main/src/main/java/dev/mccue/json/JsonNumber.java) instead of a bespoke toNumber. You need the extra methods to go to big decimal and co, but it's just an extension to the behavior of Number at that point. * JsonObject and JsonArray could implement Map<String, Json> and List<Json> respectively. This lowers the need for toUntyped() - since presumably one of the use cases for that is turning the json tree into something that more generic map/list traversal code can handle. It also complicates any lazy loading somewhat. * Assuming patterns can be placed on interfaces, you might want to consider something similar to JsonDecoder, but with a pattern instead of a method that throws an exception. // Where here fromJson would box up the logic for testing and extracting from each element in the array. List<Person> people = array(json, Person::fromJson); * I don't think there is sufficient cause for anything to be non-sealed at this point. * JsonBoolean and JsonNull do not have reasonable alternative implementations - as far as I can imagine, maybe i'm wrong - so maybe those can just be final classes? * If you seal up the whole hierarchy then its pretty trivial to make it serializable ( https://github.com/bowbahdoe/json/blob/main/src/main/java/dev/mccue/json/serialization/JsonSerializationProxy.java ) On Thu, May 15, 2025 at 11:29 PM Remi Forax <fo...@univ-mlv.fr> wrote: > Hi Paul, > yes, not having a simple JSON API in Java is an issue for beginners. > > It's not clear to me why JsonArray (for example) has to be an interface > instead of a record ? > > I understand why Json.parse() only works on String and char[] but the API > make it too easy to have many performance issues. > I think you need versions using a Reader and a Path. > Bonus point, if there is a method walk() that also returns a JsonValue but > the List/Map inside JsonArray/JsonObject are populated lazily. > > Minor point: Json.toDisplayString() should takes a second parameters > indicating the number of spaces used for the indentation (like > JSON.stringify in JS). > > regards, > Rémi > > ----- Original Message ----- > > From: "Paul Sandoz" <paul.san...@oracle.com> > > To: "core-libs-dev" <core-libs-dev@openjdk.org> > > Sent: Thursday, May 15, 2025 10:30:42 PM > > Subject: Towards a JSON API for the JDK > > > Hi, > > > > We would like to share with you our thoughts and plans towards a JSON > API for > > the JDK. > > Please see the document below. > > > > - > > > > We have had the pleasure of using a clone of this API in some > experiments we are > > conducting with > > ONNX and code reflection [1]. Using the API we were able to quickly > write code > > to ingest and convert > > a JSON document representing ONNX operation schema into instances of > records > > modeling the schema > > (see here [2]). > > > > The overall out-of-box experience with such a minimal "batteries > included” API > > has so far been positive. > > > > Thanks, > > Paul. > > > > [1] https://openjdk.org/projects/babylon/ > > [2] > > > https://github.com/openjdk/babylon/blob/code-reflection/cr-examples/onnx/opgen/src/main/java/oracle/code/onnx/opgen/OpSchemaParser.java#L87 > > > > # Towards a JSON API for the JDK > > > > One of the most common requests for the JDK is an API for parsing and > generating > > JSON. While JSON originated as a text-based serialization format for JSON > > objects ("JSON" stands for "JavaScript Object Notation"), because of its > simple > > and flexible syntax, it eventually found use outside the JavaScript > ecosystem as > > a general data interchange format, such as framework configuration files > and web > > service requests/response formats. > > > > While the JDK cannot, and should not, provide libraries for every > conceivable > > file format or protocol, the JDK philosophy is one of "batteries > included", > > which is to say we should be able to write basic programs that use common > > protocols such as HTTP, without having to appeal to third party > libraries. > > The Java ecosystem already has plenty of JSON libraries, so inclusion in > > the JDK is largely meant to be a convenience, rather than needing to be > the "one > > true" JSON library to meet the needs of all users. Users with specific > needs > > are always free to select one of the existing third-party libraries. > > > > ## Goals and requirements > > > > Our primary goal is that the library be simple to use for parsing, > traversing, > > and generating conformant JSON documents. Advanced features, such as data > > binding or path-based traversal should be possible to implement as > layered > > features, but for simplicity are not included in the core API. We adopt > a goal > > that the performance should be "good enough", but where performance > > considerations conflict with simplicity and usability, we will choose in > favor > > of the latter. > > > > ## API design approach > > > > The description of JSON at `https:://json.org` describes a JSON > document using > > the familiar "railroad diagram": > >  > > > > This diagram describes an algebraic data type (a sum of products), which > we > > model directly with a set of Java interfaces: > > > > ``` > > interface JsonValue { } > > interface JsonArray extends JsonValue { List<JsonValue> values(); } > > interface JsonObject extends JsonValue { Map<String, JsonValue> > members(); } > > interface JsonNumber extends JsonValue { Number toNumber(); } > > interface JsonString extends JsonValue { String value(); } > > interface JsonBoolean extends JsonValue { boolean value(); } > > interface JsonNull extends JsonValue { } > > ``` > > > > These interfaces have (hidden) companion implementation classes that > admit > > greater flexibility of implementation than modeling them directly with > records > > would permit. > > Further, these interfaces are unsealed. We compromise on the sealed sum > of > > products to enable > > alternative implementations, for example to support alternative formats > that > > encode the same information in a JSON document but in a more efficient > form than > > text. > > > > The API has static methods for parsing strings into a `JsonValue`, > conversion to > > and from purely untyped representations (lists and maps), and factory > methods > > for building JSON documents. We apply composition consistently, e.g, a > > JsonString has a string, a JsonObject has a map of string to JsonValue, > as > > opposed to extension for structural JSON values. > > > > It turns out that this simple API is almost all we need for traversal. > It gives > > us an immutable representation of a document, and we can use pattern > matching to > > answer the myriad questions that will come up (Does this object have key > X? Does > > it map to a number? Is that number representable as an integer?) when > going > > from an untyped format like JSON to a more strongly typed domain model. > > Given a simple document like: > > > > ``` > > { > > "name": "John”, > > "age": 30 > > } > > ``` > > > > we can parse and traverse the document as follows: > > > > ``` > > JsonValue doc = Json.parse(inputString); > > if (doc instanceof JsonObject o > > && o.members().get("name") instanceof JsonString s > > && s.value() instanceof String name > > && o.members().get("age") instanceof JsonNumber n > > && n.toNumber() instanceof Long l && l instanceof int age) { > > // use "name" and "age" > > } > > ``` > > > > Later, when the language acquires the ability to expose deconstruction > patterns > > for arbitrary interfaces (similar to today's record patterns, see > > > https://openjdk.org/projects/amber/design-notes/patterns/towards-member-patterns > ), > > this will be simplifiable to: > > > > ``` > > JsonValue doc = Json.parse(inputString); > > if (doc instanceof JsonObject(var members) > > && members.get("name") instanceof JsonString(String name) > > && members.get("age") instanceof JsonNumber(int age)) { > > // use "name" and "age" > > } > > ``` > > > > So, overtime, as more pattern matching features are introduced we > anticipate > > improved use of the API. This is a primary reason why the API is so > minimal. > > Convenience methods we add today, such as a method that accesses a JSON > > object component as say a JSON string or throws an exception, will become > > redundant in the future. > > > > ## JSON numbers > > > > The specification of JSON number makes no explicit distinction between > integral > > and decimal numbers, nor specifies limits on the size of those numbers. > > This is a common source of interoperability issues when consuming JSON > > documents. Generally users cannot always but often do assume JSON > numbers are > > parsable, without loss of precision, to IEEE double-precision floating > point > > numbers or 32-bit signed integers. > > > > In this respect the API provides three means to operate on the JSON > number, > > giving the user full control: > > > > 1. Underlying string representation can be obtained, if preserving > syntactic > > details such as leading or trailing zeros is important. > > 2. The string representation can be parsed to an instance of > `BigDecimal`, using > > `toBigDecimal` if preserving decimal numbers is important. > > 3. The string representation can be parsed into an instance of `Long`, > `Double`, > > `BigInteger`, or `BigDecimal`, using `toNumber`. The result of this > method > > depends on how the representation can be parsed, possibly losing > precision, > > choosing a suitably convenient numeric type that can then be pattern > > matched on. > > > > Primitive pattern matching will help as will further pattern matching > features > > enabling the user to partially match. > > > > ## Prototype implementation > > > > The prototype implementation is currently located into the JDK sandbox > > repository > > under the `json` branch, see > > here > > > https://github.com/openjdk/jdk-sandbox/tree/json/src/java.base/share/classes/java/util/json > > The prototype API javadoc generated from the repository is also > available at > > > https://cr.openjdk.org/~naoto/json/javadoc/api/java.base/java/util/json/package-summary.html > > > > ### Testing and conformance > > > > The prototype implementation passes all conformance test cases but two, > > available > > on https://github.com/nst/JSONTestSuite. The two exceptions are the > ones which > > the > > prototype specifically prohibits, i.e, duplicated names in JSON objects > > (https://cr.openjdk.org/~naoto/json/conformance/results/parsing.html#35 > ). > > > > ### Performance > > > > Our main focus so far has been on the API design and a functional > > implementation. > > Hence, there has been less focus on performance even though we know > there are a > > number of performance enhancements we can make eventually. > > We are reasonably happy with the current performance. The > > implementation performs well when compared to other JSON implementations > > parsing from string instances and traversing documents. > > > > An example of where we may choose simplicity and usability over > performance > > is the rejection of JSON documents containing objects that in turn > contain > > members > > with duplicate names. That may increase the cost of parsing, but > simplifies the > > user > > experience for the majority of cases since if we reasonably assume > JsonObjects > > are > > map-like, what should the user do with such members, pick one the last > one? > > merge > > the values? or reject? > > > > ## A JSON JEP? > > > > We plan to draft JEP when we are ready. Attentive readers will observe > that > > a JEP already exists, JEP 198: Light-Weight JSON API > > (https://openjdk.org/jeps/198). We will > > either update this JEP, or withdraw it and draft a new one. >