Update JSON builders to provide more mutable/iterable operations #2308
Description
What is your use-case and why do you need this feature?
The JSON array and object builders are very useful, but their operations are restricted to only add elements. This makes it more difficult to perform additional mutations (like removing elements), or collection-based operations like iterating over the values.
Implementing all of the possible List-specific and Map-specific operations can be easily achieved via delegation.
Missing operations require in unclear workarounds
For example, there's no a clear way of how to remove elements at the moment. Without a default method method, it's easy to
-
come up with complicated workarounds that aren't easy to read, or scale
val obj = buildJsonObject { put("a", 1) put("b", 2) put("c", 3) } println(obj) println( // if I'm in a rush I might quickly hack this, which is confusing // and incurs technical/documentation debt JsonObject(obj.toMap().run { minus("b") }) ) println( // if I had more time, this is a little more clear JsonObject(obj.toMap() - "b") // but it requires some deeper understanding how Kotlin/KxS works, // more than should be necessary for a basic operation )
-
accidentally write code that looks correct, but introduces bugs
println( // On first glance this looks like it might work obj.toMutableMap().minus("b") // but it returns a MutableMap, not a JsonObject )
-
or (for Kotlin / KxS beginners) get confused and give up!
Mutable/immutable JSON element mismatch
There is also a mis-match between the JSON builders and the read-only classes they build.
-
JsonObject
implementsMap<String, JsonElement>
via delegation
-
JsonArray
implementsList<JsonElement>
via delegation
It makes sense to update the JSON builders so they work similarly, which helps with understanding and reduces the difference between the two.
Sharing methods
It would also help understanding if JsonObject
/JsonArray
shares as much functionality as possible with MutableMap
/MutableList
, because learning one helps with learning to use the other.
Describe the solution you'd like
- introduce
MutableJsonArray
andMutableJsonObject
classes MutableJsonArray
extendsJsonArray
and implementsMutableList<JsonElement>
using delegationMutableJsonObject
extendsJsonObject
and implementsMutableMap<String, JsonElement>
using delegation- Remove
JsonArrayBuilder
, and replace it with the newMutableJsonArray
- Remove
JsonObjectBuilder
, and replace it with the newMutableJsonObject
Example:
public class JsonArrayBuilder @PublishedApi internal constructor(
private val content: MutableList<JsonElement> = mutableListOf()
): MutableList<JsonElement> by content
I also see two further improvements:
Mutable variants
Currently the JSON object/array builders are internal
and are only used for the builder operations, but why is this? Since they would become specialised implementations of a mutable collection, why not make them publicly accessible? I propose the following:
- Rename
JsonArrayBuilder
toMutableJsonArray
, - and
JsonObjectBuilder
toMutableJsonObject
, - and update the constructors to be
public
.
This would allow users to create and define mutable JSON objects and arrays, using a class that would also be shared with the buildJsonObject {}
and buildJsonArray {}
helpers.
Update extension functions?
If JsonArrayBuilder implements MutableList<JsonElement>
, then all of the JsonArrayBuilder
extension functions could be adapted to extend MutableList<JsonElement>
instead of JsonArrayBuilder
.
public fun MutableList<JsonElement>.add(value: Number?): Boolean = add(JsonPrimitive(value))
This would improve the usability of KxS in more situations, making the KxS more convenient to use.
See also
-
I wonder if we should just add
addAll
or simply make JsonArrayBuilder implementMutableList
(by delegation, like with JsonArray or by implementingAbstractMutableList
)Originally posted by @sandwwraith in
JsonArrayBuilder
- add convenience function for adding multiple elements #2127 (comment) -
It would be nice to provide builders that comply with such expectations and where
remove*
functions are added deliberately (e.g. notbuildList {}
where it just comes with theMutableList
contract), and the overall prior art overview.For example,
Guava
, which is arguably one of the most used libraries, doesn't provideremove*
with their builders (ImmutableMap.Builder should have remove method google/guava#3596) , Scala's builder doesn't seem to have one and so onOriginally posted by @qwwdfsad in add function for removing entry in
JsonObjectBuilder
#2191 (comment)