dfabulich
4 hours ago
A stated goal of the API is to have "low ceremony"; this seems like a lot of ceremony.
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"))));nlitened
4 hours ago
There's a nice Java library called Clojure with a lightweight syntax if you need to work with data structures and concurrency in Java a lot:
(println (json/generate-string {:providers ["SUN" "SunRsaSign" "SunEC"]}))packetlost
2 hours ago
You really can't appreciate how awesome Clojure is until you're coming from Java, can you...
pron
an hour ago
And vice versa. As someone who likes both languages, I appreciate how they both have their pros and cons. I was a Schemer before I learnt Java (when Java barely existed) and I adore Clojure, but if I were to write 10 MLOC mobile network routing and billing system, an air-traffic control system, or a credit-card transaction processing system etc. etc. that needs to evolve by a large team for 20 years, I would choose Java over Clojure any day. So I came to Java from C, C++, Scheme, and ML, and it's completely unsurprising to me why there has never existed a language that better supports large, long-lasting, important software (I'm not saying such a language couldn't exist, but it currently doesn't). And it's not just about the features Java has and, just as importantly, doesn't have (and if you think keeping features out of a programming language is easy, think again), but also how its evolution is handled.
BoorishBears
36 minutes ago
I prefer its sibling library Kotlin, from the makers of the world famous Java IDE: Jetbrains Fleet
wduquette
12 minutes ago
A stated goal of the API is to have "low ceremony" when reading arbitrary JSON files. I agree the JSON creation API looks pretty clunky, but the parsing API looks fairly simple.
dtech
3 hours ago
Java lacks the expressiveness to make it much better than this. There's a reason almost all the "replacement Java" languages add something to support DSLs, e.g. type safe builders in Kotlin [1]
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.htmlpron
3 hours ago
That's not the reason. The question was about JSON generation from existing Java types, not about a DSL. Java JSON libraries offer similar conveniences (in fact, you can be much more sophisticated, clear, and convenient than the DSL you've shown), but this particular library, as explicitly described in the JEP, is not intended to be a general-purpose JSON library, of which Java already has several good and popular ones.
dtech
3 hours ago
That's not what the person I replied to asked at all, arbitrary types weren't mentioned.
They literally asked why they couldn't write JsonObject.of(Map.of(...))
pron
3 hours ago
And there's nothing in Java preventing that. In fact, it is trivial to implement that exact API on top of the API proposed in the JEP.
pron
3 hours ago
I'm not involved with the design of this API, but it seems to me that the issue on the JSON generation side (where you're complaining about ceremony) is feature creep. Creating the API you want on top of the proposed one is trivial (even in user code), but then where do you stop? If you have that conversion, it seems reasonable to also support, say, sets and records; maybe even enums.
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.
dfabulich
3 hours ago
POJOs and records require more configuration (e.g. Jackson's @JsonProperty, @JsonDeserialize). That could plausibly be out of scope.
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.)
pron
an hour ago
> there's really not that much scope to creep into
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.
HiJon89
2 hours ago
> 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.
What type would that method accept? Wouldn't it need to be Object? That seems worse than boilerplate
gavinray
an hour ago
FWIW, in Kotlin's native JSON library, the API is almost identical.
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)owlstuffing
an hour ago
You can inline native type-safe JSON directly in Java with the manifold project.
/*[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/manifoldfallingbananna
12 minutes ago
You could... but I view JSON as a simple format that you could write a recursive parser for in a few hundred lines yourself.
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.
mechanicum
4 hours ago
You almost can, with Jackson:
jshell> new ObjectMapper().valueToTree(Map.of("providers", List.of("SUN", "SunRsaSign", "SunEC")));
$4 ==> {"providers":["SUN","SunRsaSign","SunEC"]}
(or was that the joke?)singron
3 hours ago
Currently, JsonObject.of has this signature:
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).
hyperpape
2 hours ago
A Modest Proposal that will never be implemented: add an interface to String, Boolean, Integer, Long, Float and Double. Because all these types already implement "toString" (and I believe their toString representations are compatible with JSON), it can be a pure marker interface.
patrickthebold
an hour ago
JSON String requires escaping of control characters. .toString() on a String is (hopefully) the identity.
dfabulich
an hour ago
For the "quick one-off script" case (where Implicitly Declared Classes shine), I think the most galling ceremony in this example is explicitly converting all N items in a list of literals into JsonValues for JsonArray.
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")
))
));vlaaad
4 hours ago
Yes, what's the point of JsonObject at all? Why JsonString when there is String? Why JsonArray when there is List? JDK only needs a function to convert from JSON format to the normal data structures it already has and back.
pron
3 hours ago
Because JSON types don't cleanly map to Java types. For example, a JSON number could be an int, a long, a double, a BigInteger, or a BigDecimal. Which of them the Java code wants is not something you can deduce (most generally, you could represent all JSON numbers as BigDecimal, but that's not very convnient in Java code, so in most cases you'd want to convert that to a primitive type of your choosing anyway). A JSON array is heterogeneous, while Java code typically want to be homogeneous. Representing a JSON array as a `List<Object>` won't work because the conversion of the element types is ambiguous (e.g. numbers).
prpl
3 hours ago
Yeah you could go with just Number though, and then keep almost everything a BigInteger/BigDecimal under the covers (or dynamically choose the correct class)
pron
2 hours ago
You could, but it would be significantly worse. Number and JsonNumber are nearly equivalent in this context, except that JsonNumber has the major advantage of being in the same sealed hierarchy as the other JSON types, allowing for nice pattern-matching (and BigDecimal suffers from the same downside). So you gain nothing - you have to convert to the desired type anyway - and lose quite a bit.
Hackbraten
3 hours ago
Good point for parsing. However, the example is about serialization. What mapping ambiguity do you see there?
pron
an hour ago
There it's not about ambiguity: https://news.ycombinator.com/item?id=49025644
dtech
3 hours ago
It's pretty normal, almost all libs work this way. This way you can parse the JSON string into the matching data structure or print it into a string, and when building you can ensure it's valid JSON.
marginalia_nu
3 hours ago
Yeah, I agree.
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?
pron
an hour ago
The motivation behind this JEP is laid out in the JEP under the section "Motivation".
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).
BoingBoomTschak
an hour ago
I was going to make fun of Java before even opening the page with something to the tune of `AbstractBeanJsonSimpleFactory` but looks like reality beat me to it, heh.
RedShift1
4 hours ago
minimal-json scratches this itch really well for me. Dead simple API. Unfortunately not maintained anymore, but I'm still using it, even for new projects, because it just works so well.
cute_boi
3 hours ago
Even Rust syntax is so much clean and better than this...