I keep tripping up when trying to construct an options-object for passing to a javascript-library.
I want to use classes rather than external interface because they are less verbose and typesafe in construction.
The problem is they often don’t work, because their prototype doesn’t descend from normal new Object() and therefore has no methods like hasOwnProperty.
Any thought on this?
This works
val options = (js("new Object()") as Any).apply {
asDynamic().theme = "test"
}
This doesn’t work
class QOption(val theme: String)
val options2 = QOptions(
theme = "test"
)
Wrong. There’s no hasOwnProperty in JavaScript objects. It’s a property of Object (not instances!). I don’t know why they don’t work in your particular case. Where are you going to pass options? What about classes, declared in other languages, like ES6 or TypeScript, do they work normally in your use case?
Responding to an old thread, but just in case others trip over this.
I believe, the main problem is that Kotlin generates js code that always initializes all fields of a class. You can initialize with null and unitialized, but you can never leave a field unassigned.
Many js libraries on the other hand, will only test if a value is assigned and will then try to cast it (without checking for null), especially if libraries that iterate over options by keys.
I use the following solution:
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
/**
* Support for strongly typed creation of dynamic JS objects.
*
* Usage:
* object MyOptions: DynamicType {
* val foo by number
* val bar by string
* val baz by obj(Other::class)
* }
* external fun something(options: Dynamic<MyOptions>)
* fun test() {
* something(MyOptions.create {
* it[foo] = 1
* })
* }
*/
interface DynamicType
class PropertyType<S: DynamicType, T> {
inline operator fun getValue(obj: Any, property: KProperty<*>) = Property<S, T>(property.name)
}
inline class Property<S: DynamicType, T>(val name: String)
val <S: DynamicType> S.any get() = PropertyType<S, Any>()
val <S: DynamicType> S.number get() = PropertyType<S, Number>()
val <S: DynamicType> S.boolean get() = PropertyType<S, Boolean>()
val <S: DynamicType> S.string get() = PropertyType<S, String>()
fun <S: DynamicType, T: Any> S.obj(type: KClass<T>) = PropertyType<S, T>()
fun <S: DynamicType, T: Any> S.array(type: KClass<T>) = PropertyType<S, Array<T>>()
inline fun <S:DynamicType> S.create(initializer: S.(Dynamic<S>) -> Unit)
= Dynamic<S>().also { initializer(it) }
class Dynamic<S: DynamicType> {
operator fun <T> set(key: Property<S, T>, value: T) {
val obj: dynamic = this
obj[key.name] = value
}
}