首页 > 解决方案 > 基于 GSON 的 DSL 导致 NPE

问题描述

我一直在尝试创建一个 Kotlin DSL,以使用类似 JSON 的语法创建 GSON JsonObjects。我的构建器看起来像这样

import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive

class JsonBuilder(builder: JsonBuilder.() -> Unit) {
    init {
        builder()
    }

    val result = JsonObject()

    infix fun String.to(property: Number) = result.addProperty(this, property)
    infix fun String.to(property: Char) = result.addProperty(this, property)
    infix fun String.to(property: Boolean) = result.addProperty(this, property)
    infix fun String.to(property: String) = result.addProperty(this, property)
    infix fun String.to(property: JsonElement) = result.add(this, property)
    infix fun String.to(properties: Collection<JsonElement>) {
        val arr = JsonArray()
        properties.forEach(arr::add)
        result.add(this, arr)
    }
    operator fun String.invoke(builder: JsonObject.() -> Unit) {
        val obj = JsonObject()
        obj.builder()
        result.add(this, obj)
    }
}

fun json(builder: JsonBuilder.() -> Unit) = JsonBuilder(builder).result

我的测试看起来像这样

fun main() {
    val json = json {
        "name" to "value"
        "obj" {
            "int" to 1
        }
        "true" to true
    }
    println(json)
}

但是,在执行时,它会导致 NullPointerException 指向使用的第一个 String 扩展函数,我觉得它的描述性不是很强,因为我没有看到任何可以为空的东西。此外,我看不出它与当然不会导致 NPE 的常规执行有何不同。

    val json = JsonObject()
    json.addProperty("name", "value")
    val obj = JsonObject()
    obj.addProperty("int", 1)
    json.add("obj", obj)
    json.addProperty("true", true)

我的问题是究竟是什么导致了异常(以及如何防止它)。

标签: kotlingsondsl

解决方案


问题是您在结果对象之前指定了初始化程序块,导致在您使用它时它为空 - 这可以通过以下(代码的反编译输出)可视化。

   public JsonBuilder(@NotNull Function1 builder) {
         Intrinsics.checkParameterIsNotNull(builder, "builder");
         super();
         builder.invoke(this);
         this.result = new JsonObject();
   }

因此,解决方案是将结果的声明和初始化移到初始化块之前。

class JsonBuilder(builder: JsonBuilder.() -> Unit) {
    val result = JsonObject()

    init {
        builder()
    }

    // ...
}

而现在的结果...

{"name":"value","int":1,"obj":{},"true":true}

编辑:您还希望允许与您的 DSL 链接,并修复您当前拥有的错误。

    operator fun String.invoke(builder: JsonBuilder.() -> Unit) {
        val obj = JsonBuilder(builder).result
        result.add(this, obj)
    }

这会产生正确的结果

{"name":"value","obj":{"int":1},"true":true}

推荐阅读