JEP 540: Simple JSON API (Now in Incubator)
Posted by theanonymousone 1 day ago
Comments
Comment by dfabulich 1 day ago
IO.println(JsonObject.of(Map.of("providers",
JsonArray.of(List.of(JsonString.of("SUN"),
JsonString.of("SunRsaSign"),
JsonString.of("SunEC"))))));
There's gotta be a better way!Surely there could be some way of creating a JsonArray of native Java Strings, Booleans, Doubles, and Integers without requiring clients to explicitly convert each value into a JsonValue. And why am I forced to convert a native List into a JsonArray just so I can make it the value of a JsonObject?
Why can't I write this?
JsonObject.of(Map.of("providers", List.of("SUN", "SunRsaSign", SunEC"))));Comment by nlitened 1 day ago
(println (json/generate-string {:providers ["SUN" "SunRsaSign" "SunEC"]}))Comment by ramblurr 8 hours ago
Comment by packetlost 1 day ago
Comment by pron 1 day ago
In this particular case, of course it is more straightforward to embed what is effectively an untyped language in another untyped language than in a typed language, at least while keeping the language simple and without adding features we don't wish to add (even in TypeScript, working with JSON directly requires bypassing type checks). Still, as the JEP says, there are good, popular Java libraries that offer more convenient and powerful ways of working with JSON, but that is not the purpose of this particular package.
Comment by nesarkvechnep 1 day ago
Comment by agumonkey 1 day ago
Comment by layer8 1 day ago
Comment by BoorishBears 1 day ago
Comment by pjmlp 17 hours ago
"The next thing is also fairly straightforward: we expect Kotlin to drive the sales of IntelliJ IDEA. We’re working on a new language, but we do not plan to replace the entire ecosystem of libraries that have been built for the JVM. So you’re likely to keep using Spring and Hibernate, or other similar frameworks, in your projects built with Kotlin. And while the development tools for Kotlin itself are going to be free and open-source, the support for the enterprise development frameworks and tools will remain part of IntelliJ IDEA Ultimate, the commercial version of the IDE. And of course the framework support will be fully integrated with Kotlin."
https://blog.jetbrains.com/kotlin/2011/08/why-jetbrains-need...
Comment by vips7L 14 hours ago
Comment by jamesfinlayson 1 day ago
Comment by dtech 1 day ago
The kotlin stdlib-adjacent json lib does it like this
buildJsonObject {
putJsonArray("providers") {
add("SUN")
add("SunRsaSign")
add("SunEC")
}
}
[1] https://kotlinlang.org/docs/type-safe-builders.htmlComment by layer8 1 day ago
That's not really true. You can have a Java library providing:
Json.writeTo(System.out)
.object()
.array("providers")
.element("SUN")
.element("SunRsaSign")
.element("SunEC")
.end()
.end();
or as a shortcut Json.writeTo(System.out)
.object()
.array("providers").withElements("SUN", "SunRsaSign", "SunEC")
.end();
or similar (aka faceted fluent API), with typed interfaces mirroring the grammar of the target language, here JSON.This is basically a structured output iterator (or OutputStream-like) pattern. It can also support modularization such that substructures can be factored out into separate methods or lambdas, thereby also allowing loops and conditionals.
Such an API can be provided in a relatively compact fashion and would be nicer than what the JEP proposes.
Comment by pron 1 day ago
Comment by dtech 1 day ago
They literally asked why they couldn't write JsonObject.of(Map.of(...))
Comment by pron 1 day ago
Comment by vips7L 14 hours ago
Comment by vips7L 11 hours ago
sealed Interface A {
class B: A
class C: A
companion object {
operator fun invoke(s: String) = when (s) {
"B" -> B()
"C" -> C()
}
}
}
val a = A("B")Comment by gavinray 1 day ago
val json = JsonObject(
mapOf(
"providers" to JsonArray(
listOf(
JsonPrimitive("SUN"),
)
)
)
)
println(json)
Of course nobody generally does it this way, usually you take a List/Map and directly serialize that with a helper val data = mapOf("providers" to listOf("SUN", "SunRsaSign", "SunEC"))
val kotlinxJSON = Json.encodeToJsonElement(data)
val jacksonJSON = ObjectMapper().writeValueAsString(data)Comment by pron 1 day ago
Indeed, Java JSON libraries typically offer all these conveniences and more, but this package is not intended to replace them - as the JEP clearly states ("It is not a goal to create an API that supplants established external JSON libraries"). It is intended to support only simple JSON tasks without requiring a library. For that goal, feature creep is particularly problematic: the more you offer (which reduces the need for the popular libraries in more situations) the greater the pressure to add even more.
Often, it's best to start with the minimum required, and then, as the API gets used in the field, see what the most valuable convenience methods to add on top.
Comment by dfabulich 1 day ago
But for constructing JSON out of strings, numbers, booleans, lists, and maps, there's really not that much scope to creep into.
Specifically, I think it would be perfectly cromulent to have JsonArray.of() be able to support any Iterable of native Strings, Integers, Doubles, or Booleans; that doesn't feel like feature creep to me at all. It would transparently support Sets. (Right now, the API only accepts Lists of JsonValues, which is what makes the API feel so ceremonious.)
Comment by pron 1 day ago
First, we've been in this game far too long to know that this isn't the case. Second, this is only incubation. It may well be that the team behind this feature intend to add more convenience methods but wish to do it later once the core is more battle-tested. It's always best to focus on the core first and add ornamentation once you know the core is right.
Comment by ptx 12 hours ago
Comment by pron 5 hours ago
Comment by HiJon89 1 day ago
What type would that method accept? Wouldn't it need to be Object? That seems worse than boilerplate
Comment by peterabbitcook 1 day ago
In my world, I think the worst possible outcome would be a java.util.json library that does 50-95% of what I currently do with GSON or Jackson. In that scenario I still need an external dependency, and I can either ignore java.util.json or turn a codebase into an error-prone mix of both.
Maybe a good set of design considerations for java.util.json would be “what does this API need to run a CRUD app written in modern java?” Parse requests from the web, send responses to the web, and serialize/deserialize data from X database (e.g. if noSQL or jsonb). In my head “in modern java” would mean that JSON is converted to records at application boundaries
Comment by pron 1 day ago
Reading the JEP, that is not the motivation. The target is more a short script or some REPL interaction that reads JSON data from a web service and does something with it. A CRUD app likely already uses a web framework that comes with a full JSON library.
Comment by singron 1 day ago
static JsonObject of(Map<String, ? extends JsonValue> map);
java.lang.String and other types don't extend JsonValue, and java lacks any trait-like way to add this functionality to existing types, so you would have to change the signature to this: static JsonObject of(Map<String, ? extends Object> map);
Now you can pass any Object in, but the typechecker can't ensure that it is convertible to json anymore. I.e. it will have to check at runtime that it's either JsonValue or another type that is has a known conversion for (Integer, Double, String, List, Map, etc.). The jackson ObjectMapper e.g. has a lot of configuration available to tell it how to do these conversions on arbitrary types, and I think they want to eliminate that kind of ceremony.It does seem like any serious application is going to use another library, and this will be useful for very simple json usage or single-file hello-world type programs (e.g. to go along with Implicitly Defined Classes and the Flexible Launch Protocol).
Comment by svieira 12 hours ago
And that ladies and gentlemen is what Java's re-doing of TypeClasses (called "witness" in the experiments they're doing now) are for.
Comment by hyperpape 1 day ago
Comment by thayne 1 day ago
Not quite. String is the big problem, since it needs to be wrapped in quotes, and special characters need to be escaped. But Float and Double are also problematic because Infinity and NaN aren't representable in json.
However, the new interface could have a "toJsonString" or maybe even a toJson method that returns a JsonValue
Comment by patrickthebold 1 day ago
Comment by dfabulich 1 day ago
This would help a lot:
public interface JsonArray extends JsonValue {
static JsonArray of(JsonValue... elements) { ... }
static JsonArray of(String... elements) { ... }
static JsonArray of(Double... elements) { ... }
static JsonArray of(Integer... elements) { ... }
static JsonArray of(Boolean... elements) { ... }
}
Then, you could at least write: IO.println(JsonObject.of(Map.of("providers",
JsonArray.of("SUN", "SunRsaSign", "SunEC"))));
And for JsonObject, a little fluent builder API would probably knock out a lot of ceremony, too. JsonObject json = JsonObject.builder()
.put("name", "John")
.put("age", 30)
.put("active", true)
.put("providers", JsonArray.of("SUN", "SunEC"))
.build();
The alternative today looks quite ceremonious: JsonObject json= JsonObject.of(Map.of(
"name", JsonString("John"),
"age", JsonNumber(30),
"active", JsonBoolean(true),
"providers", JsonArray.of(List.of(
JsonString("SUN"),
JsonString("SunRsaSign"),
JsonString("SunEC")
))
));Comment by mechanicum 1 day ago
jshell> new ObjectMapper().valueToTree(Map.of("providers", List.of("SUN", "SunRsaSign", "SunEC")));
$4 ==> {"providers":["SUN","SunRsaSign","SunEC"]}
(or was that the joke?)Comment by vlaaad 1 day ago
Comment by pron 1 day ago
Comment by prpl 1 day ago
Comment by pron 1 day ago
Comment by Hackbraten 1 day ago
Comment by pron 1 day ago
Comment by dtech 1 day ago
Comment by Someone 1 day ago
I think so to, and it surprises me that they write
“A key goal driving the recent evolution of the Java Platform has been to enable simple tasks to be accomplished more easily and with less ceremony. Features serving this goal include convenience factory methods for collections […]”
and don’t, from that, decide that this API needs such factory methods.
The API also doesn’t make JsonObject or JsonArray collections, requiring one to use ‘asMap’ or ‘asArray’ before iterating over them.
What benefit does that carry? That one can implement those interfaces as records?
Comment by owlstuffing 1 day ago
/*[Dude.json/] {
"Name": "Scott",
"Age": 100,
"Address": {
"Street": "345 Syracuse Way",
"City": "Atlantis"
}
}
*/
Dude dude = Dude.fromSource();
out.println(dude.getName());
out.println(dude.getAge());
out.println(dude.getAddress().getCity());
https://github.com/manifold-systems/manifoldComment by fallingbananna 1 day ago
Bringing in compile-time code generation just for defining static JSONs (which is not that common outside of tests, as most JSONs are serialized and deserialized at runtime) sounds like a hard sell.
Comment by owlstuffing 1 day ago
Comment by marginalia_nu 1 day ago
The clean way of doing this is to build a json marshaling mechanism for the type system as it already exists. This is doable in Java, and some json libraries (e.g. gson) are already capable of this.
I must admit I don't fully understand the motivation behind this JEP. Like when would I ever reach for this?
Comment by pron 1 day ago
As I understand that section (and I wasn't involved in writing this JEP), good and popular marshalling libraries for JSON already exist, and the JEP clearly states that it is not the goal to replace them or perform their role. The JEP says that this package may be what you'd reach for when a program only wants to do some very simple, small tasks with JSON data and the requirements and code size don't merit pulling in a fully-featured JSON library (e.g. when you're writing a one-file script, or exploring in JShell).
Comment by wduquette 1 day ago
Comment by RedShift1 1 day ago
Comment by BoingBoomTschak 1 day ago
Comment by cute_boi 1 day ago
Comment by whaley 1 day ago
One of my favorite things about Jackson was being able to arbitrarily navigate through the document with a rich and fluent API (JsonNode) in jackson.databind, which this JEP at least conceptually borrows from with the JsonValue abstraction. Both of these are better than how some of the other implementations do it, where you are effectively working with glorified Map<String,Object>
Comment by rf15 1 day ago
Comment by wewtyflakes 1 day ago
Comment by svieira 1 day ago
Comment by whartung 1 day ago
I already have one of these (I'm sure I'm not alone). It's about 500 lines.
I found I'm not a huge fan of the JAX-B-ish style serialization of Java objects for JSON. I don't want to really downplay them, they certainly have their uses, they're very popular, I just don't like fighting them. Hand writing JSON marshaling code has not been arduous for me (notably with my utility layer). (I also, philosophically, strive to avoid "magic" in my code as much as practical.)
Of course, I still need a parser, I'm using GSONs parser, which means I'm still dragging in the whole bean level serialization infrastructure. I just don't use it. And while I've done JSON parsers before, I felt it was something better to import than maintain myself. So, in that sense, it's a mixed bag.
But, I do enjoy using it.
This will be a worthwhile JDK capability. Ideally it can replace mine.
Comment by nikeee 1 day ago
String body = ... REST response body, which is a JSON document ... ;
JsonValue json = Json.parse(body);
json.get("properties").get("periods").asList().stream()
.mapToInt(j -> j.get("temperature").asInt())
.average()
.ifPresent(IO::println);
Why am I able to call `.get(string)` or `.get(int)` on a JsonValue? Shouldn't these be on the JsonObject and JsonArray instead?> If the JsonValue instance is of the wrong type, or if the requested member or element does not exist, the access methods throw a JsonValueException.
So if I get an exception, I can't tell whether the value was of the wrong type or the object didn't have the requested key? This looks like a footgun to me.
They just brought pattern matching to Java. Why not move those .get to JsonArray and JsonObject? That would solve this confusions. So we could just use something like `if (json instanceof JsonObject o) o.get("properties")`
Comment by jaen 16 hours ago
json.asObject().get("prop1").asObject().get("prop2")...Comment by zbentley 1 day ago
Comment by gavinray 1 day ago
1. An HTTP server library/framework
2. A JSON library
We got a decently-performing and unopionated HTTP server in JDK 18 with "HttpHandlers" and "SimpleFileServer" plus "jwebserver" CLI
It later received Virtual Thread support, which made performance + scalability very competitive.
With a JSON module, you finally won't NEED to rely on external deps to build a basic JVM web service without pain.
Now, we just need a proper CLI framework like picocli, or at least "argparse" from Python stdlib...
Comment by drdexebtjl 1 day ago
A web server using this could only start parsing when it receives the last byte, and could only start responding when it’s done serializing, all while holding non-lazy trees of JsonValue objects in memory.
Comment by wewtyflakes 1 day ago
Comment by gavinray 1 day ago
I'm sure lots of them exist, but for your typical CRUD API, this has not been a phenomena I've run into.
Comment by drdexebtjl 1 day ago
A non-streaming implementation needs to copy the request from the network stack into a contiguous GC-managed char array, possibly resizing it a few times as the data is received. Then when it’s time to parse, it goes through this array and allocates an unbounded number of JsonValue nodes. For JsonString and JsonNumber, it probably needs to create defensive copies of the data instead of spans of the input array, otherwise changing the input array corrupts the tree.
That’s kinda bad under memory pressure even for benign inputs. But consider malicious inputs, such as {"x":{"x":{"x":{"x":{"x":{…}}}}}. It would make this non-streaming implementation allocate a lot of String and JsonObject instances. The allocations would total multiple times the size of the input, and would be extremely fragmented.
On the other hand, a library that does streaming and that binds to objects could parse straight from the buffers in the network stack, and could avoid allocating objects for anything that it will not need to bind.
Comment by Sankozi 1 day ago
Comment by Groxx 1 day ago
I'm somewhat boggled that json5 hasn't grown to be more of a thing.
Comment by IanGabes 1 day ago
I find it interesting to note that nowhere in this JEP is the word "serialization", which is what most people might associate with JSON libs. Or rather, they are studiously ignoring that feature and just improving the ergonomics of interacting with JSON.
Comment by mcfedr 1 day ago
Comment by ameliaquining 1 day ago
Comment by esprehn 1 day ago
This seems to repeat the same mistakes of Go's built in JSON library where the ecosystem is full of workarounds and other libraries that are faster or have better features.
Comment by zbentley 1 day ago
As similar as all the almost-JSON formats are, I still think it's best to keep APIs single-purpose: one for JSON, one for JSON-lines, one for JSONC, and so on. It's a larger code surface, but a less potentially surprising one.
Comment by jonenst 1 day ago
Comment by crewindream 1 day ago
If parsing fails - how often can you do anything else than just abort and log/show an error..
Checked exceptions are nice in theory, but it is very context dependent if checked’ness is useful, I would prefer that libraries do not expose them.
Comment by MeteorMarc 1 day ago
Comment by q3k 1 day ago
Does anyone know how this behaves when encountering a repeated key in an object? (RFC8259 states that keys SHOULD be unique, which makes that generally allowed and implementations all behave slightly differently).
Comment by lmz 1 day ago
Duplicate keys are a parse exception.
> Additionally, documents must not have objects with duplicate member names.
Comment by q3k 1 day ago
Good, that's the least bad behavior. (Postel's law be damned)
Comment by exabrial 1 day ago
Comment by delusional 1 day ago