Search code examples
javascriptdynamickotlinkotlin-js-interop

What is simple way to convert dynamically Kotlin/Js objects to plain javascript object?


For example, we have this structure:

data class Item(
        val city: String,
        val name: String
)

val structure = mapOf("items" to listOf(
                Item("NY", "Bill"),
                Item("Test", "Test2"))

)

And I want to get this object in Javascript:

var structure = {
  "items": [
    {
      "city": "NY",
      "name": "Bill"
    },
    {
      "city": "Test",
      "name": "Test2"
    }
  ]
}

How we could convert map from Kotlin to dynamic type with such structure in Javascript?

I find only this explicit way:

fun Map<String, Any>.toJs(): dynamic {
    val result: dynamic = object {}

    for ((key, value) in this) {
        when (value) {
            is String -> result[key] = value
            is List<*> -> result[key] = (value as List<Any>).toJs()
            else -> throw RuntimeException("value has invalid type")
        }
    }

    return result
}

fun List<Any>.toJs(): dynamic {
    val result: dynamic = js("[]")

    for (value in this) {
        when (value) {
            is String -> result.push(value)
            is Item -> result.push(value.toJs())
            else -> throw RuntimeException("value has invalid type")
        }
    }

    return result
}

fun Item.toJs(): dynamic {
    val result: dynamic = object {}

    result["city"] = this.city
    result["name"] = this.name

    return result
}

I know that is possible to do this also with serialization/deserialization, but I think it will be slower and with some overhead.

Does anybody know simple way to covert Kotlin object to plain Javascript object (dynamic Kotlin type)?


Solution

  • You can do that using this simple function

    inline fun <I> objectOf(
        jsonObject: I = js("new Object()").unsafeCast<I>(),
        writer: I.() -> Unit
    ): I {
        writer(jsonObject)
        return jsonObject
    }
    

    Usage:

    interface Structure {
        var items: Array<Item>
    
        interface Item {
            var city: String
            var name: String
        }
    }
    
    
    fun main() {
            val structure0 = objectOf<dynamic> {
                items = arrayOf<dynamic>(
                    objectOf {
                        city = "NY"
                        name = "Bill"
                        orAnything = "Literly, anything!"
                    },
                    objectOf { city = "Test"; name = "Test2" }
                )
            }
            println(JSON.stringify(structure0))
            // {"items":[{"city":"NY","name":"Bill","orAnything":"Literly, anything!"},{"city":"Test","name":"Test2"}]}
        
            val structure1 = objectOf<Structure> {
                items = arrayOf(
                    objectOf {
                        city = "NY"
                        name = "Bill"
        //                orAnything = "Literly anything" // Compile time Error: Unresolved reference: orAnything
                    },
                    objectOf {
                        city = "Test"
                        name = "Test2"
                    }
                )
            }
        
            println(JSON.stringify(structure1))
            // {"items":[{"city":"NY","name":"Bill"},{"city":"Test","name":"Test2"}]}
        
            val structure2 = objectOf(structure1) {
                items.forEach {
                    it.city = "Khartoum"
                }
            }
            println(JSON.stringify(structure2))
            // {"items":[{"city":"Khartoum","name":"Bill"},{"city":"Khartoum","name":"Test2"}]}
        
            val structure3 = objectOf(structure0) {
                items.unsafeCast<Array<dynamic>>().forEach {
                    it.name = "Shalaga44"
                }
            }
            println(JSON.stringify(structure3))
            //{"items":[{"city":"NY","name":"Shalaga44","orAnything":"Literly, anything!"},{"city":"Test","name":"Shalaga44"}]}
        
        }