Olaf Gottschalk
08/03/2026, 5:13 AMclass TinyBuilder {
val data: Map<String, Any?>
field = mutableMapOf<String, Any?>()
infix fun <http://String.to|String.to>(value: Any?) {
data[this] = value
}
infix fun <http://String.to|String.to>(list: TinyList1D) {
data[this] = list.elements
}
// Adding this 2D builder adds functionality for deeper nesting, but will kill the option to set
// `to []` due to overload resolution ambiguity, interestingly for all three functions
// value: Any?, list: TinyList1D and list: TinyList2D!
infix fun <http://String.to|String.to>(list: TinyList2D) {
data[this] = list.elements
}
// this is a "fix" I tried, making clear that [] can be assigned only here, but it makes things worse!
// infix fun <http://String.to|String.to>(emptyList: List<Nothing>) {
// data[this] = emptyList<Any?>()
// }
}
class TinyList1D(val elements: List<Map<String, Any?>>) {
companion object {
operator fun of(vararg builders: TinyBuilder.() -> Unit) =
TinyList1D(builders.map { build(it) })
}
}
class TinyList2D(val rows: List<TinyList1D>) {
val elements by lazy { rows.map { it.elements} }
companion object {
operator fun of(vararg row: TinyList1D) = TinyList2D(row.toList())
}
}
fun build(builder: TinyBuilder.() -> Unit): Map<String, Any?> =
TinyBuilder().apply(builder).data
fun main() {
println(
build {
"foo" to "bar"
}
) // prints {foo=bar}
println(
build {
"foo" to [1, 2, 3, 4]
"empty" to []
}
) // prints {foo=[1, 2, 3, 4],empty=[]}
println(
build {
"foo" to [1, 2, 3, 4]
"nested" to [{"deeper" to "yes"}]
}
) // prints {foo=[1, 2, 3, 4],nested=[{deeper=yes}]}
println(
build {
"2d nested" to [[{"deepest" to "yeah"}]]
}
) // prints {2d nested=[[{deepest=yeah}]]}
}
You need to copy this to a project with -Xcollection-literals enabled!
So, I want to make use of collection literals to make my JSON builder DSL look even more like natural JSON. Of course, the : in real JSON are not reproducible, but the rest is getting quite close.
A user can now add lists of scalars like this:
build {
"foo" to [1, 2, 3, 4]
}
And thanks to the new operator fun of, I even managed a list of objects without much trouble, thanks to one to function that accepts the `TinyList1D`:
build {
"foo" to [
{"bar" to 1}, {"bar" to 2}
]
}
But here's where things get strange. Empty arrays are supported indirectly through this first `TinyBuilder1D`: "foo" to [] is resolved here, and creates an empty list (even though the overload chosen is the one for deeper object nesting!)
When I add a second to overload for 2D arrays that are about to enable "foo" to [[{"deeper" to "yes"}]], the 2D nesting works just fine, but the empty array now breaks down, signaling "overload resolution ambiguity" for all three functions: the plain Any?, the TinyList1D and TinyList2D!
I wanted to fix this by adding a new overload specifically for empty lists: List<Nothing> I thought must be 100% compatible to [] - but no, this one makes things even worse.
So, overall: I was only able to play this game for 1D lists of objects. Not further. And I don't know if this is something that could be fixed before the collection literals become stable?!?
Thanks for any input!Olaf Gottschalk
08/03/2026, 8:56 AMGrigorii Solnyshkin
08/04/2026, 4:05 PM[] is expected: indeed, all three candidates for to (with Any?, TinyList1D , and TinyList2D) are considered successful: `TinyList`s declare their own operator of and Any? can fallback to List.
◦ Among these candidates, neither TinyList1D is more specific than TinyList2D nor TinyList2D is more specific than TinyList1D. That's why ambiguity is reported.
◦ Both of these candidates are more specific than one with Any?. However, in the diagnostic message all successful candidates are included.
• Good news is that there is a nice workaround for your case: you can modify a signature of TinyList2D.Companion.of slightly:
class TinyList2D(val rows: List<TinyList1D>) {
val elements by lazy { rows.map { it.elements} }
companion object {
operator fun of(first: TinyList1D, vararg row: TinyList1D) = TinyList2D(row.toList())
}
}
That way, empty collection literal will be inferred to TinyList1D (as it is more specific than Any?). If you want it to be inferred specifically to List<Nothing> you can also modify TinyList1D in the same way and add an overload with List<Nothing>.Olaf Gottschalk
08/04/2026, 4:14 PMGrigorii Solnyshkin
08/05/2026, 6:18 AMTinyList2D.Companion.of then for empty collection literal the corresponding to candidate just becomes unsuccessful, so it is not actually about specificity (it is filtered out before choosing the most specific candidate)