Bernhard
10/05/2026, 9:41 AMbuildRecord {
name = "test"
address {
street = "s"
}
}
should result in
{
"name": "test",
"address.street": "s"
}
looking into delegated properties, it looks like delegates can't implement any other operator funs except for setValue/getValue? e.g. this does not seem to work when address is a delegated property
buildRecord {
name = "test"
address["street"] ="s"
}
is that correct?min
10/05/2026, 9:55 AMimport kotlin.reflect.*
fun main() {
val address by Address()
kotlin.test.assertEquals(mapOf(), address)
address["street"] = "foo"
kotlin.test.assertEquals(
mapOf<String, Any>(Pair("street", "foo")),
address,
)
}
class Address(val map: MutableMap<String, Any> = mutableMapOf()) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): MutableMap<String, Any> {
return this.map
}
}min
10/05/2026, 9:58 AMoperator fun {g, s}et should be implemented by what the delegate getValue method returns. Also I’m sure your question only contains a minimal relevant example and leaves out a lot of context, but unless buildRecord does some meaningful heavy lifting, I’d just use object literals.
object {
val name = "test"
val address = object {
val street = "s"
}
}Bernhard
10/05/2026, 9:59 AMBernhard
10/05/2026, 9:59 AMBernhard
10/05/2026, 9:59 AMBernhard
10/05/2026, 10:00 AMbuildRecord {
property = "test"
-property
}Bernhard
10/05/2026, 10:00 AMBernhard
10/05/2026, 10:01 AMbuildRecord {
property.set("test")
property.delete()
}Bernhard
10/05/2026, 10:03 AMmin
10/05/2026, 10:04 AM{"property": _del}I don’t create objects like this in JS, so I’m afraid I don’t understand your end goal and can’t help you much. Why don’t you just create a type that wraps
Map<JsKey, JsValue> and define methods on it?Bernhard
10/05/2026, 10:05 AMBernhard
10/05/2026, 10:06 AMBernhard
10/05/2026, 10:07 AMmin
10/05/2026, 10:09 AM.set() with = saves 4 keystrokes. I’d think twice about whether saving 4 keystrokes is worth adding a whole lot of complexity to buildRecordBernhard
10/05/2026, 10:10 AMBernhard
10/05/2026, 10:11 AMBernhard
10/05/2026, 10:12 AMephemient
10/05/2026, 3:48 PMpackage com.example
internal annotation class ValueContainer
@ValueContainer
class Key(public val id: String) {
override fun toString(): String = id
}
class RecordBuilder<V> private constructor(
private val map: MutableMap<String, V>,
private val path: List<Key>,
) {
constructor() : this(mutableMapOf(), emptyList())
fun Key.assign(value: V) {
map[path.plus(this@assign).joinToString(".")] = value
}
operator fun Key.invoke(block: RecordBuilder<V>.() -> Unit) {
RecordBuilder(map, path.plus(this@invoke)).apply(block)
}
fun Key.delete() {
map.remove(path.plus(this@delete).joinToString("."))
}
fun toMap(): Map<String, V> = map.toMap()
}
fun <V> buildRecord(block: RecordBuilder<V>.() -> Unit): Map<String, V> = RecordBuilder<V>().apply(block).toMap()
then a consumer could use
plugins {
id("org.jetbrains.kotlin.plugin.assignment") version "2.4.20"
}
assignment {
annotation("com.example.ValueContainer")
}
in its build.gradle.kts and then write code like
val name = Key("name")
val address = Key("address")
val street = Key("street")
buildRecord {
name = "test"
address.delete()
address {
street = "s"
}
}Bernhard
10/05/2026, 3:50 PMephemient
10/05/2026, 3:50 PMephemient
10/05/2026, 3:51 PMval with types annotated by @org.gradle.api.SupportsKotlinAssignmentOverloading in *.gradle.kts, as kotlin-dsl uses the plugin and configures it for its annotationBernhard
10/05/2026, 3:51 PM