用于创建json对象的Kotlin DSL(不创建垃圾)
我正在尝试创建一个用于创建JSONObject的DSL。以下是生成器类和示例用法:用于创建json对象的Kotlin DSL(不创建垃圾),kotlin,Kotlin,我正在尝试创建一个用于创建JSONObject的DSL。以下是生成器类和示例用法: import org.json.JSONObject fun json(build: JsonObjectBuilder.() -> Unit): JSONObject { val builder = JsonObjectBuilder() builder.build() return builder.json } class JsonObjectBuilder { va
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)
}
它按预期工作。但每次创建json对象时,它都会创建一个额外的JsonObjectBuilder实例。是否可以编写一个DSL来创建json对象而无需额外的垃圾?您可以使用一个堆栈,用一个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)
}
在一个
JsonObjectBuilder
上跨多个线程调用json
和build
可能会有问题,但这对于您的用例来说不应该是问题。您需要DSL吗?您将失去强制执行String
键的能力,但vanilla Kotlin并没有那么糟糕:)
是的,如果您不需要节点的任何中间表示,并且上下文总是相同的(递归调用彼此没有区别),那么这是可能的。这可以通过立即写入输出来完成 然而,这严重增加了代码的复杂性,因为您必须立即处理DSL调用,而不将它们存储在任何地方(同样,为了避免冗余对象) 示例(参见其演示): 如果您不需要缩进,只需要有效的JSON,那么这可以很容易地简化
您可以使用
json{}
和.toJson{}
函数内联
,甚至消除lambda类,从而实现几乎零的对象开销(一个JsonContext
和StringBuilder
及其缓冲区仍然被分配),但这需要您更改这些函数使用的成员的可见性修饰符:公共内联函数只能访问public
或@PublishedApi internal
成员。我不确定我是否正确回答了这个问题。你不想要一个建筑工人
import org.json.JSONArray
import org.json.JSONObject
class Json() {
private val json = JSONObject()
constructor(init: Json.() -> Unit) : this() {
this.init()
}
infix fun String.to(value: Json) {
json.put(this, value.json)
}
infix fun <T> String.to(value: T) {
json.put(this, value)
}
override fun toString(): String {
return json.toString()
}
}
fun main(args: Array<String>) {
val json = Json {
"name" to "Roy"
"body" to Json {
"height" to 173
"weight" to 80
}
"cars" to JSONArray().apply {
put("Tesla")
put("Porsche")
put("BMW")
put("Ferrari")
}
}
println(json)
}
找到了另一个解决方案。您只需继承
JSONObject
类,而无需创建其他对象。
UPD:如果您使用gson库,您可以查看以下内容。它不会产生任何垃圾,源代码易于阅读和理解。您可以使用类似的库来构建json
val myJson = json {
"size" to 0
"array" to arrayOf(1,2,3)
"aggs" to {
"num_destinations" to {
"cardinality" to {
"field" to "DestCountry"
}
}
}
}
免责声明:我是该库的作者。Kotlin必须创建一个函数对象以传递到
json{…}
,因此“不创建额外对象”问题从一开始就是有缺陷的。JVM在优化短期对象方面非常有效。除非您对代码进行了基准测试,并且100%确定创建JSONObjectBuilder
实例会限制您的性能,否则我根本不会担心这一点。(个人提示:我会让你的构建器成为一个接口,并将实际实现隐藏在一个私有类中,这样你就不会暴露json字段。)是的,json{…}
应该是inline
你是否为这些位创建了一个人工制品?请参阅github.com/holgerbrandl/jsonbuilder,获取一个小型DSL,以使用kotlinI创建json同意,这个版本看起来已经像DSL了。我非常喜欢这个在Java中创建JSON的解决方案,因为它使用Kotlin已经很干净的DSL来创建类似JSON的数据,但它的开销与问题中的示例相同。每个mapOf
创建一个新的Map
,然后为每个JSONObject
(包括顶级对象中嵌套的JSONObject
)复制并丢弃该映射。我喜欢堆栈,但这不正是一个问题(即每次调用json()时都会创建一个JSONObjectBuilder和JSONObject)
)?不过,我认为您的思路是正确的-您只需要记住上次调用JSONObject.put()
的结果,就可以对嵌套字段调用put()
,而不是创建新的JSONObject
。我希望这是有道理的!:)上面的示例只创建了一个JSONObject Builder和两个JSONObject,这比我提出的解决方案要好得多。唯一的问题是最终的json字符串的顺序是相反的。@HoundDog,在org.json.JSONObject
API中,您必须创建一个新的JSONObject
,以便将其嵌套在另一个对象中,因此我不相信这里有任何其他对象。我希望这有意义@ilkinulas我打印了我的解决方案,结果与您在问题中的示例输出相同。我不相信JSONObject
会维护键的顺序,尽管从一次运行到另一次运行,您可能会得到不同的键顺序。JSON键没有排序(想想HashMap
和LinkedHashMap
),太棒了!这是一个有趣的解决方案,但是类型在最后的json字符串中丢失了。每个值都转换为字符串。@ilkinulas您可以用${JSONObject.valueToString(value)}
替换\“$value\”
,以保留类型。这非常优雅。您的答案看起来不错,但是关于JSONArray呢?我是kotlin的初学者,所以,你能给我解释一下如何处理JSONArray吗kotlin@KishanDonga我不确定你想要实现什么。我更新了我的答案,您也可以分配JSONArray。
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"
}
}
}
import org.json.JSONArray
import org.json.JSONObject
class Json() {
private val json = JSONObject()
constructor(init: Json.() -> Unit) : this() {
this.init()
}
infix fun String.to(value: Json) {
json.put(this, value.json)
}
infix fun <T> String.to(value: T) {
json.put(this, value)
}
override fun toString(): String {
return json.toString()
}
}
fun main(args: Array<String>) {
val json = Json {
"name" to "Roy"
"body" to Json {
"height" to 173
"weight" to 80
}
"cars" to JSONArray().apply {
put("Tesla")
put("Porsche")
put("BMW")
put("Ferrari")
}
}
println(json)
}
{
"name": "Roy",
"body": {
"weight": 80,
"height": 173
},
"cars": [
"Tesla",
"Porsche",
"BMW",
"Ferrari"
]
}
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}
val myJson = json {
"size" to 0
"array" to arrayOf(1,2,3)
"aggs" to {
"num_destinations" to {
"cardinality" to {
"field" to "DestCountry"
}
}
}
}