KotlinDSL用于创建json对象(不创建垃圾)

mspsb9vt  于 2023-01-13  发布在  Kotlin
关注(0)|答案(6)|浏览(108)

我正在尝试创建一个DSL来创建JSONObjects。下面是一个builder类和一个示例用法:

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 "xxx@yyy.com"
                }
            }
    println(jsonObject)
}

以上代码的输出为:

{"contact":{"city":"istanbul","email":"xxx@yyy.com"},"name":"ilkin","age":37,"male":true}

它按预期工作。但是它在每次创建json对象时都会创建一个额外的JsonObjectBuilder示例。有没有可能编写一个DSL来创建json对象而不产生额外的垃圾?

50pmv0ei

50pmv0ei1#

您可以使用Deque作为堆栈,通过单个JsonObjectBuilder跟踪当前的JSONObject上下文:

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 "xxx@yyy.com"
                }
            }
    println(jsonObject)
}

输出示例:

{"contact":{"city":"istanbul","email":"xxx@yyy.com"},"name":"ilkin","age":37,"male":true}

在单个JsonObjectBuilder上跨多个线程调用jsonbuild可能会有问题,但这对您的用例来说应该不是问题。

z9gpfhce

z9gpfhce2#

你需要DSL吗?你失去了强制String密钥的能力,但是香草Kotlin并没有那么糟糕:)

JSONObject(mapOf(
        "name" to "ilkin",
        "age" to 37,
        "male" to true,
        "contact" to mapOf(
                "city" to "istanbul",
                "email" to "xxx@yyy.com"
        )
))
pkln4tw6

pkln4tw63#

2023年1月11日更新:
infix fun String.to(json: Json -> Unit)替换为infix fun String.to(json: Json.() -> Unit)infix fun String.to(json: Json.() -> Unit)使用Json块作为接收器,并在创建Json对象后调用。因此,无需在Json对象中添加Json键。
我不确定我是否理解错了这个问题。你不想要一个建筑工人?

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)

}

你会得到

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

mcvgt66p4#

是的,如果你不需要任何节点的中间表示,并且上下文总是相同的(递归调用彼此没有区别),这是可能的。这可以通过立即写输出来完成。
但是,这严重增加了代码的复杂性,因为您必须立即处理DSL调用,而不将它们存储在任何地方(同样,为了避免冗余对象)。
示例(请参见此处的演示):

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("}")
    }
}
fun json(block: JsonContext.() -> Unit) = JsonContext().run {
    block()
    "{\n" + output.toString() + "\n}"
}
val j = json {
    "a" to 1
    "b" to "abc"
    "c" toJson {
        "d" to 123
        "e" toJson {
            "f" to "g"
        }
    }
}

不过,如果您不需要缩进,而只需要有效的JSON,这可以很容易地简化。
你可以让json { }.toJson { }函数inline甚至摆脱lambda类,这样你就几乎实现了零对象开销(一个JsonContextStringBuilder及其缓冲区仍然被分配),但是这需要你改变这些函数使用的成员的可见性修饰符:公共内联函数只能访问public@PublishedApi internal成员。

tpxzln5u

tpxzln5u5#

找到了另一个解决方案。你可以直接继承JSONObject类而不需要创建其他对象。

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 "xxx@yyy.com"
                }
            }
    println(jsonObject)
}

代码的输出将是相同的。

{"contact":{"city":"istanbul","email":"xxx@yyy.com"},"name":"ilkin","age":37,"male":true}

UPD:如果你使用gson库,你可以看看这个awesome library。它不会产生任何垃圾,源代码很容易阅读和理解。

wpx232ag

wpx232ag6#

您可以使用https://github.com/holgerbrandl/jsonbuilder之类的库来构建json

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

免责声明:我是这个库的作者。

相关问题