Why Kotlin does not support overloading of the assignment operator "="?

Your solution is nice, but my goal with the DSL was to fluently build up JSON using Google’s JsonObject from GSON. There was no need of reassigning the values, so your solution has unnecessary boilercode. I tried to fit as much to the JSON format as possible:

fun jsonObject(obj: JsonObject = JsonObject(), op: JsonObjectBuilder.() -> Unit) = JsonObjectBuilder(obj).apply(op).build()

fun jsonArray(arr: JsonArray = JsonArray(), op: JsonArrayBuilder.() -> Unit) = JsonArrayBuilder(arr).apply(op).build()

fun jsonArray(items: Iterable<Any?>): JsonArray {
    val res = JsonArray()
    items.forEach { addToArray(res, it) }
    return res
}


fun jsonArray(vararg items: Any?): JsonArray {
    val res = JsonArray()
    items.forEach { addToArray(res, it) }
    return res
}

private fun addToArray(res: JsonArray, it: Any?) {
    if (it == null) res.add(JsonNull.INSTANCE)
    else when (it) {
        is String -> res.add(JsonPrimitive(it))
        is Number -> res.add(JsonPrimitive(it))
        is Boolean -> res.add(JsonPrimitive(it))
        is JsonObject -> res.add(it)
        is JsonArray -> res.add(it)
        else -> res.add(JsonPrimitive(it.toString()))
    }
}

class JsonObjectBuilder(private val obj: JsonObject = JsonObject()) {

    infix operator fun String.plusAssign(value: Int) = obj.addProperty(this, value)
    infix operator fun String.plusAssign(value: Long) = obj.addProperty(this, value)
    infix operator fun String.plusAssign(value: Boolean) = obj.addProperty(this, value)
    infix operator fun String.plusAssign(value: String) = obj.addProperty(this, value)
    infix operator fun String.plusAssign(value: Double) = obj.addProperty(this, value)
    infix operator fun String.plusAssign(value: JsonElement) = obj.add(this, value)
    infix operator fun String.plusAssign(value: Any?) =
            if (value == null)
                obj.add(this, JsonNull.INSTANCE)
            else obj.addProperty(this, value.toString())

    // This allows to write "subobject" { } instead of "subobject" += jsonObject {}, but I rarely use it
    operator fun String.invoke(op: (JsonObjectBuilder) -> Unit) = obj.add(this, JsonObjectBuilder().apply(op).build())

    fun build() = obj
}

class JsonArrayBuilder(private val arr: JsonArray = JsonArray()) {

    fun add(other: Number) = arr.add(JsonPrimitive(other))

    operator fun Boolean.unaryPlus() = arr.add(JsonPrimitive(this))
    operator fun String.unaryPlus() = arr.add(JsonPrimitive(this))
    fun NULL() = arr.add(JsonNull.INSTANCE)
    operator fun JsonElement.unaryPlus() = arr.add(this)
    operator fun Any?.unaryPlus() = if (this == null) NULL() else arr.add(JsonPrimitive(this.toString()))

    fun build() = arr
}

and with it, you can write something like this:

val json : JsonObject = jsonObject {
    "alma" += 4
    "citrom" += null
    "3" += "null"
    "o" += jsonObject {    // I could write the short form, too: "o" {
        "b" += true
    }
    "arr" += jsonArray {
        // One can add any business logic within
        (0..10).forEach { +"Value $it" }

        +"alma"
        +jsonObject { }
    }
    "a2" += jsonArray(1, null, "lama")
   // Call the toJson of subobject
   "sub" += subObject.toJson()
}

This also allows extensible JSON building (used in inheritance):

open class Parent( val x : Int ) {
   open fun toJson() = jsonObject {
      "x" += x
   }
}

class Child(x : Int, val y : Boolean) : Parent(x) {
   override fun toJson() = jsonObject(super.toJson()) {
      "y" += y
   }
}

println( Child( 1, true ).toJson() )   // Prints { "x" : 1, "y" : true }

Both your and my solution is correct but aims different goals.

1 Like