Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 219 Vote(s) - 3.43 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Kotlin DSL for creating json objects (without creating garbage)

#1
I am trying to create a DSL for creating JSONObjects. Here is a builder class and a sample usage:

import org.json.JSONObject

fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
val builder = JsonObjectBuilder()
builder.build()
return builder.json
}

class JsonObjectBuilder {
val json = JSONObject()

infix fun <T> String.To(value: T) {
json.put(this, value)
}
}

fun main(args: Array<String>) {
val jsonObject =
json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To json {
"city" To "istanbul"
"email" To "[email protected]"
}
}
println(jsonObject)
}


The output of the above code is :

{"contact":{"city":"istanbul","email":"[email protected]"},"name":"ilkin","age":37,"male":true}

It works as expected. But it creates an additional JsonObjectBuilder instance every time it creates a json object. Is it possible to write a DSL for creating json objects without extra garbage?
Reply

#2
Do you need a DSL? You lose the ability to enforce `String` keys, but vanilla Kotlin isn't that bad :)

JSONObject(mapOf(
"name" to "ilkin",
"age" to 37,
"male" to true,
"contact" to mapOf(
"city" to "istanbul",
"email" to "[email protected]"
)
))
Reply

#3
Yes, it is possible if you don't need any intermediate representation of the nodes, and if the context is always the same (the recursive calls are no different from each other). This can be done by writing the output immediately.

However, this severely increases code complexity, because you have to process your DSL calls right away without storing them anywhere (again, to avoid redundant objects).

Example (see its demo [here][1]):

class JsonContext internal constructor() {
internal val output = StringBuilder()

private var indentation = 4

private fun StringBuilder.indent() = apply {
for (i in 1..indentation)
append(' ')
}

private var needsSeparator = false

private fun StringBuilder.separator() = apply {
if (needsSeparator) append(",\n")
}

infix fun String.to(value: Any) {
output.separator().indent().append("\"$this\": \"$value\"")
needsSeparator = true
}

infix fun String.toJson(block: JsonContext.() -> Unit) {
output.separator().indent().append("\"$this\": {\n")
indentation += 4
needsSeparator = false
block(this@JsonContext)
needsSeparator = true
indentation -= 4
output.append("\n").indent().append("}")
}
}

<sup></sup>

fun json(block: JsonContext.() -> Unit) = JsonContext().run {
block()
"{\n" + output.toString() + "\n}"
}

<sup></sup>

val j = json {
"a" to 1
"b" to "abc"
"c" toJson {
"d" to 123
"e" toJson {
"f" to "g"
}
}
}

If you don't need indentation but only valid JSON, this can be easily simplified, though.

You can make the `json { }` and `.toJson { }` functions `inline` to get rid even of the lambda classes and thus you achieve almost zero object overhead (one `JsonContext` and the `StringBuilder` with its buffers are still allocated), but that would require you to change the visibility modifiers of the members these functions use: public inline functions can only access `public` or `@PublishedApi internal` members.

[1]:

[To see links please register here]

Reply

#4
You can use a [Deque][1] as a stack to track your current `JSONObject` context with a single `JsonObjectBuilder`:

fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
return JsonObjectBuilder().json(build)
}

class JsonObjectBuilder {
private val deque: Deque<JSONObject> = ArrayDeque()

fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
deque.push(JSONObject())
this.build()
return deque.pop()
}

infix fun <T> String.To(value: T) {
deque.peek().put(this, value)
}
}

fun main(args: Array<String>) {
val jsonObject =
json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To json {
"city" To "istanbul"
"email" To "[email protected]"
}
}
println(jsonObject)
}

Example output:

<!-- language: none -->

{"contact":{"city":"istanbul","email":"[email protected]"},"name":"ilkin","age":37,"male":true}

Calling `json` and `build` across multiple threads on a single `JsonObjectBuilder` would be problematic but that shouldn't be a problem for your use case.


[1]:

[To see links please register here]

Reply

#5
Found another solution. You can just inherit `JSONObject` class without need to create other objects.
<!-- language: kotlin-->


class Json() : JSONObject() {

constructor(init: Json.() -> Unit) : this() {
this.init()
}

infix fun <T> String.To(value: T) {
put(this, value)
}
}

fun main(args: Array<String>) {
val jsonObject =
Json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To Json {
"city" To "istanbul"
"email" To "[email protected]"
}
}
println(jsonObject)
}

The output of code will be the same.

{"contact":{"city":"istanbul","email":"[email protected]"},"name":"ilkin","age":37,"male":true}

**UPD**: If you use gson library you can look at this [awesome library](

[To see links please register here]

). It doesn't create any garbage, source code is easy to read and understand.
Reply

#6
You could use a library such as

[To see links please register here]

to build json with
```
val myJson = json {
"size" to 0
"array" to arrayOf(1,2,3)
"aggs" to {
"num_destinations" to {
"cardinality" to {
"field" to "DestCountry"
}
}
}
}
```

Disclaimer: I'm the author of the library.
Reply

#7
Updated on Jan 11 2023:<br/>
Replaced `infix fun String.to(json: Json -> Unit)` with `infix fun String.to(json: Json.() -> Unit)` which uses Json block as receiver and invokes after a Json object is created. So no longe need to add Json key inside Json object.

I am not sure if I get the question correctly. You don't want a builder?

import org.json.JSONArray
import org.json.JSONObject

class Json() : JSONObject() {

constructor(init: Json.() -> Unit) : this() {
this.init()
}

infix fun String.to(json: Json.() -> Unit) {
put(this, Json().apply(json))
}

infix fun <T> String.to(value: T) {
put(this, value)
}

infix fun <T> String.to(values: List<T>) {
put(this, JSONArray().apply {
values.forEach { put(it) }
})
}
}

fun main(args: Array<String>) {

val json = Json {
"name" to "Roy"
"body" to {
"height" to 173
"weight" to 80
}
"cars" to listOf(
"Tesla"
"Porsche"
"BMW"
"Ferrari"
)
}

println(json)

}

You will get

{
"name": "Roy",
"body": {
"weight": 80,
"height": 173
},
"cars": [
"Tesla",
"Porsche",
"BMW",
"Ferrari"
]
}
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through