用于创建json对象的Kotlin DSL(不会创建垃圾)

我正在尝试创建一个用于创建JSONObjects的DSL。 这是一个建造者类和一个示例用法:

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对象没有额外的垃圾?

您可以使用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} 

调用json并在单个JsonObjectBuilder多个线程上build会有问题,但这对您的使用情况应该不成问题。

你需要一个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" ) )) 

是的,如果不需要节点的任何中间表示,并且上下文始终相同(递归调用彼此没有区别),则可以。 这可以通过立即写入输出来完成。

但是,这会严重增加代码的复杂性,因为您必须立即处理您的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,这可以很容易地简化,但。

您可以使inline json { }.toJson { }函数来摆脱lambda类,从而实现几乎为零的对象开销(一个JsonContext和带有其缓冲区的StringBuilder仍然被分配),但这需要您更改这些函数使用的成员的可见性修饰符:public inline函数只能访问public@PublishedApi internal成员。

我不知道如果我正确地得到问题。 你不想要一个建设者?

 class Json() { val json = JSONObject() constructor(init: Json.() -> Unit) : this() { this.init() } infix fun <T> String.To(value: T) { json.put(this, value) } override fun toString(): String { return json.toString() } } 

你可以这样做:

 val json = Json { "name" To "Roy" "body" To Json { "height" To 173 "weight" To 80 } } println(json) 

{"name":"Roy","body":"{\"weight\":80,\"height\":173}"}