<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">
Hi Cay,
<div><br>
</div>
<div>It would be really helpful to share some more detailed use-cases on editing/modification that you may reasonably expect users to perform.</div>
<div><br>
</div>
<div>I hope we might be able to devise a transformation API, hopefully layered on top of the public API and possibly with structural sharing for unmodified parts. One such experiment is presented below that copies the flat map transformation patterns used by
 the class file API and code reflection (a combination of traversal + building). It can be used like this:</div>
<div><br>
</div>
<div>
<div>        JsonObject o = ...</div>
<div><br>
</div>
<div>        System.out.println(transformObject(o, (jc, c) -> {</div>
<div>            if (jc instanceof JsonObjectEntry(var name, JsonNumber n) && name.equals("UnitPrice")) {</div>
<div>                // Replace number</div>
<div>                c.accept(new JsonObjectEntry(name, JsonNumber.of(n.value().doubleValue() + 10.0)));</div>
<div>            } else {</div>
<div>                // Copy</div>
<div>                c.accept(jc);</div>
<div>            }</div>
<div>        }));</div>
</div>
<div><br>
</div>
<div>This needs a lot more thought but there might be something to this.</div>
<div><br>
</div>
<div>Paul.</div>
<div><br id="lineBreakAtBeginningOfMessage">
<div>
<div style="background-color: rgb(255, 255, 255); color: rgb(8, 8, 8);">
<pre style="font-family: "JetBrains Mono", monospace; font-size: 9.8pt;"><span style="color: rgb(0, 51, 179);">public class </span><span style="color: rgb(0, 0, 0);">JsonTransform </span>{<br>    <span style="color: rgb(0, 51, 179);">public sealed interface </span><span style="color: rgb(0, 0, 0);">JsonComponent </span>{<br>    }<br><br>    <span style="color: rgb(0, 51, 179);">public record </span><span style="color: rgb(0, 0, 0);">JsonObjectMember</span>(<span style="color: rgb(0, 0, 0);">String </span>name, <span style="color: rgb(0, 0, 0);">JsonValue </span>v) <span style="color: rgb(0, 51, 179);">implements </span><span style="color: rgb(0, 0, 0);">JsonComponent </span>{<br>    }<br><br>    <span style="color: rgb(0, 51, 179);">public record </span><span style="color: rgb(0, 0, 0);">JsonArrayElement</span>(<span style="color: rgb(0, 0, 0);">JsonValue </span>v) <span style="color: rgb(0, 51, 179);">implements </span><span style="color: rgb(0, 0, 0);">JsonComponent </span>{<br>    }<br><br>    <span style="color: rgb(0, 51, 179);">public static </span><span style="color: rgb(0, 0, 0);">JsonObject </span><span style="color: rgb(0, 98, 122);">transformObject</span>(<span style="color: rgb(0, 0, 0);">JsonObject o</span>, <span style="color: rgb(0, 0, 0);">BiConsumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>, <span style="color: rgb(0, 0, 0);">Consumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>>> <span style="color: rgb(0, 0, 0);">f</span>) {<br>        <span style="color: rgb(140, 140, 140); font-style: italic;">/* value */<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">        </span><span style="color: rgb(0, 51, 179);">class </span><span style="color: rgb(0, 0, 0);">ObjectConsumer </span><span style="color: rgb(0, 51, 179);">implements </span><span style="color: rgb(0, 0, 0);">Consumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>> {<br>            <span style="color: rgb(0, 51, 179);">final </span><span style="color: rgb(0, 0, 0);">Map</span><<span style="color: rgb(0, 0, 0);">String</span>, <span style="color: rgb(0, 0, 0);">JsonValue</span>> <span style="color: rgb(135, 16, 148);">outputEntries </span>= <span style="color: rgb(0, 51, 179);">new </span>HashMap<>();<br><br>            <span style="color: rgb(0, 0, 0);">JsonObjectMember </span><span style="color: rgb(135, 16, 148);">input</span>;<br><br>            <span style="color: rgb(158, 136, 13);">@Override<br></span><span style="color: rgb(158, 136, 13);">            </span><span style="color: rgb(0, 51, 179);">public void </span><span style="color: rgb(0, 98, 122);">accept</span>(<span style="color: rgb(0, 0, 0);">JsonComponent _output</span>) {<br>                <span style="color: rgb(0, 0, 0);">JsonObjectMember output </span>= (<span style="color: rgb(0, 0, 0);">JsonObjectMember</span>) <span style="color: rgb(0, 0, 0);">_output</span>;<br><br>                <span style="color: rgb(0, 0, 0);">JsonValue outputValue</span>;<br>                <span style="color: rgb(0, 51, 179);">if </span>(<span style="color: rgb(135, 16, 148);">input </span>== <span style="color: rgb(0, 0, 0);">output</span>) {<br>                    <span style="color: rgb(140, 140, 140); font-style: italic;">// traverse<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">                    </span><span style="color: rgb(0, 0, 0);">outputValue </span>= <span style="color: rgb(0, 51, 179);">switch </span>(<span style="color: rgb(135, 16, 148);">input</span>.v()) {<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonArray ja </span>-> <span style="font-style: italic;">transformArray</span>(<span style="color: rgb(0, 0, 0);">ja</span>, <span style="color: rgb(0, 0, 0);">f</span>);<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonObject jo </span>-> <span style="font-style: italic;">transformObject</span>(<span style="color: rgb(0, 0, 0);">jo</span>, <span style="color: rgb(0, 0, 0);">f</span>);<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonValue jv </span>-> <span style="color: rgb(0, 0, 0);">jv</span>;<br>                    };<br>                } <span style="color: rgb(0, 51, 179);">else </span>{<br>                    <span style="color: rgb(140, 140, 140); font-style: italic;">// replace<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">                    </span><span style="color: rgb(0, 0, 0);">outputValue </span>= <span style="color: rgb(0, 0, 0);">output</span>.v();<br>                }<br><br>                <span style="color: rgb(135, 16, 148);">outputEntries</span>.put(<span style="color: rgb(0, 0, 0);">output</span>.name(), <span style="color: rgb(0, 0, 0);">outputValue</span>);<br>            }<br>        }<br>        <span style="color: rgb(0, 0, 0);">ObjectConsumer c </span>= <span style="color: rgb(0, 51, 179);">new </span>ObjectConsumer();<br><br>        <span style="color: rgb(0, 51, 179);">for </span>(<span style="color: rgb(0, 0, 0);">Map</span>.<span style="color: rgb(0, 0, 0);">Entry</span><<span style="color: rgb(0, 0, 0);">String</span>, <span style="color: rgb(0, 0, 0);">JsonValue</span>> <span style="color: rgb(0, 0, 0);">inputEntry </span>: <span style="color: rgb(0, 0, 0);">o</span>.members().entrySet()) {<br>            <span style="color: rgb(0, 0, 0);">JsonObjectMember input </span>= <span style="color: rgb(0, 0, 0);">c</span>.<span style="color: rgb(135, 16, 148);">input </span>= <span style="color: rgb(0, 51, 179);">new </span>JsonObjectMember(<span style="color: rgb(0, 0, 0);">inputEntry</span>.getKey(), <span style="color: rgb(0, 0, 0);">inputEntry</span>.getValue());<br>            <span style="color: rgb(0, 0, 0);">f</span>.accept(<span style="color: rgb(0, 0, 0);">input</span>, <span style="color: rgb(0, 0, 0);">c</span>);<br>        }<br><br>        <span style="color: rgb(0, 51, 179);">return </span><span style="color: rgb(0, 0, 0);">JsonObject</span>.<span style="font-style: italic;">of</span>(<span style="color: rgb(0, 0, 0);">c</span>.<span style="color: rgb(135, 16, 148);">outputEntries</span>);<br>    }<br><br>    <span style="color: rgb(0, 51, 179);">public static </span><span style="color: rgb(0, 0, 0);">JsonArray </span><span style="color: rgb(0, 98, 122);">transformArray</span>(<span style="color: rgb(0, 0, 0);">JsonArray a</span>, <span style="color: rgb(0, 0, 0);">BiConsumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>, <span style="color: rgb(0, 0, 0);">Consumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>>> <span style="color: rgb(0, 0, 0);">f</span>) {<br>        <span style="color: rgb(140, 140, 140); font-style: italic;">/* value */<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">        </span><span style="color: rgb(0, 51, 179);">class </span><span style="color: rgb(0, 0, 0);">ArrayConsumer </span><span style="color: rgb(0, 51, 179);">implements </span><span style="color: rgb(0, 0, 0);">Consumer</span><<span style="color: rgb(0, 0, 0);">JsonComponent</span>> {<br>            <span style="color: rgb(0, 51, 179);">final </span><span style="color: rgb(0, 0, 0);">ArrayList</span><<span style="color: rgb(0, 0, 0);">JsonValue</span>> <span style="color: rgb(135, 16, 148);">outputElements </span>= <span style="color: rgb(0, 51, 179);">new </span>ArrayList<>();<br><br>            <span style="color: rgb(0, 0, 0);">JsonArrayElement </span><span style="color: rgb(135, 16, 148);">input</span>;<br><br>            <span style="color: rgb(158, 136, 13);">@Override<br></span><span style="color: rgb(158, 136, 13);">            </span><span style="color: rgb(0, 51, 179);">public void </span><span style="color: rgb(0, 98, 122);">accept</span>(<span style="color: rgb(0, 0, 0);">JsonComponent _output</span>) {<br>                <span style="color: rgb(0, 0, 0);">JsonArrayElement output </span>= (<span style="color: rgb(0, 0, 0);">JsonArrayElement</span>) <span style="color: rgb(0, 0, 0);">_output</span>;<br><br>                <span style="color: rgb(0, 0, 0);">JsonValue outputValue</span>;<br>                <span style="color: rgb(0, 51, 179);">if </span>(<span style="color: rgb(135, 16, 148);">input </span>== <span style="color: rgb(0, 0, 0);">output</span>) {<br>                    <span style="color: rgb(140, 140, 140); font-style: italic;">// traverse<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">                    </span><span style="color: rgb(0, 0, 0);">outputValue </span>= <span style="color: rgb(0, 51, 179);">switch </span>(<span style="color: rgb(135, 16, 148);">input</span>.v()) {<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonArray ja </span>-> <span style="font-style: italic;">transformArray</span>(<span style="color: rgb(0, 0, 0);">ja</span>, <span style="color: rgb(0, 0, 0);">f</span>);<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonObject jo </span>-> <span style="font-style: italic;">transformObject</span>(<span style="color: rgb(0, 0, 0);">jo</span>, <span style="color: rgb(0, 0, 0);">f</span>);<br>                        <span style="color: rgb(0, 51, 179);">case </span><span style="color: rgb(0, 0, 0);">JsonValue jv </span>-> <span style="color: rgb(0, 0, 0);">jv</span>;<br>                    };<br>                } <span style="color: rgb(0, 51, 179);">else </span>{<br>                    <span style="color: rgb(140, 140, 140); font-style: italic;">// replace<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">                    </span><span style="color: rgb(0, 0, 0);">outputValue </span>= <span style="color: rgb(0, 0, 0);">output</span>.v();<br>                }<br><br>                <span style="color: rgb(135, 16, 148);">outputElements</span>.add(<span style="color: rgb(0, 0, 0);">outputValue</span>);<br>            }<br>        }<br>        <span style="color: rgb(0, 0, 0);">ArrayConsumer c </span>= <span style="color: rgb(0, 51, 179);">new </span>ArrayConsumer();<br><br>        <span style="color: rgb(0, 0, 0);">List</span><<span style="color: rgb(0, 0, 0);">JsonValue</span>> <span style="color: rgb(0, 0, 0);">values </span>= <span style="color: rgb(0, 0, 0);">a</span>.values();<br>        <span style="color: rgb(0, 51, 179);">for </span>(<span style="color: rgb(0, 51, 179);">int </span><span style="color: rgb(0, 0, 0);">i </span>= <span style="color: rgb(23, 80, 235);">0</span>; <span style="color: rgb(0, 0, 0);">i </span>< <span style="color: rgb(0, 0, 0);">values</span>.size(); <span style="color: rgb(0, 0, 0);">i</span>++) {<br>            <span style="color: rgb(140, 140, 140); font-style: italic;">// @@@ pass index?<br></span><span style="color: rgb(140, 140, 140); font-style: italic;">            </span><span style="color: rgb(0, 0, 0);">JsonArrayElement input </span>= <span style="color: rgb(0, 0, 0);">c</span>.<span style="color: rgb(135, 16, 148);">input </span>= <span style="color: rgb(0, 51, 179);">new </span>JsonArrayElement(<span style="color: rgb(0, 0, 0);">values</span>.get(<span style="color: rgb(0, 0, 0);">i</span>));<br>            <span style="color: rgb(0, 0, 0);">f</span>.accept(<span style="color: rgb(0, 0, 0);">input</span>, <span style="color: rgb(0, 0, 0);">c</span>);<br>        }<br>        <span style="color: rgb(0, 51, 179);">return </span><span style="color: rgb(0, 0, 0);">JsonArray</span>.<span style="font-style: italic;">of</span>(<span style="color: rgb(0, 0, 0);">c</span>.<span style="color: rgb(135, 16, 148);">outputElements</span>);<br>    }<br>}<br></pre>
<pre style="font-family: "JetBrains Mono", monospace; font-size: 9.8pt;"><br></pre>
</div>
<blockquote type="cite">
<div>On May 17, 2025, at 10:55 PM, Cay Horstmann <cay.horstmann@gmail.com> wrote:</div>
<br class="Apple-interchange-newline">
<div>
<div>+1 for having a JSON battery included with the JDK. And for "Our primary goal is that the library be simple to use for parsing, traversing, and generating conformant JSON documents."<br>
<br>
Generating JSON could be easier. Why not convenience methods Json.newObject and Json.newArray like in https://github.com/arkanovicz/essential-json?<br>
<br>
Parsing with instanceof will work, but is obviously painful today, as your example shows. The simplification with deconstruction patterns is not impressive either.<br>
<br>
JsonValue doc = Json.parse(inputString);<br>
if (doc instanceof JsonObject(var members)<br>
    && members.get("name") instanceof JsonString(String name)<br>
    && members.get("age") instanceof JsonNumber(int age)) {<br>
            // use "name" and "age"<br>
} else throw new NoSuchArgumentException();<br>
<br>
vs. Jackson<br>
<br>
String name = doc.get("name").asText();<br>
int age = doc.get("age").asInt();<br>
...<br>
<br>
If only there was some deconstruction magic that approximates the JavaScript code<br>
<br>
const doc = { name: "John", age: 30 }<br>
const { name, age } = doc<br>
<br>
What about editing documents? With Jackson, you can mutate objects and arrays. I see the appeal of immutability, but then there needs to be a convenient transform API. Right now, making John one year older is not pretty:<br>
<br>
var nextYearDoc = switch (doc) {<br>
   case JsonObject(var members) if<br>
       members.get("name") instanceof JsonString(String name)<br>
       && members.get("age") instanceof JsonNumber(int age)) -><br>
           Json.fromUntyped(Map.of("name", name, "age", age + 1));<br>
   default -> throw new NoSuchArgumentException();<br>
}<br>
<br>
And it gets worse if John is nested more deeply in a document.<br>
<br>
I have worked a lot with immutable XML in Scala. One minimally needs a mechanism for recursive rewriting with a node replacement function. I am not aware of an existing library that attempts this in Java for JSON. I am sure it can be done, but it may not be
 trivial to do such an API well.<br>
<br>
Cheers,<br>
<br>
Cay<br>
<br>
PS. Trying to create and show the youthful John gives me grief right now:<br>
<br>
Json.fromUntyped(Map.of("name", "John", "age", 30)).toString()<br>
|  Exception java.lang.NullPointerException: Cannot read the array length because "value" is null<br>
|        at String.rangeCheck (String.java:307)<br>
|        at String.<init> (String.java:303)<br>
|        at JsonNumberImpl.toString (JsonNumberImpl.java:105)<br>
|        at JsonObjectImpl.toString (JsonObjectImpl.java:56)<br>
|        at (#23:1)<br>
<br>
The JsonNumberImpl.toString method needs to handle the case that it was constructed from a Number.<br>
</div>
</div>
</blockquote>
</div>
<br>
</div>
</body>
</html>