A few months ago somebody here asked a question ab...
# javascript
j
A few months ago somebody here asked a question about how to convert data classes to js objects. The thread suggested just doing it manually. I'm also using kotlinx-serialization and ended up with the logic below to convert serializable data classes to and from js. This seems to work but I would appreciate any suggestions for improvements. Particularly performance improvements. I also looked at the new js-plain-objects plugin but it seems quite useless for this. I don't want to define external interfaces for all my data classes. That seems backward. Especially when they generally come from multiplatform libraries in my case. Am I missing something or is this really the only way?
Copy code
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.double
import kotlinx.serialization.json.doubleOrNull
import <http://kotlinx.serialization.json.int|kotlinx.serialization.json.int>
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject

val DEFAULT_JSON: Json = Json {
    // don't rely on external systems being written in kotlin or even having a language with default
    // values the default of false is dangerous
    encodeDefaults = true
    // save space
    prettyPrint = false
    // people adding things to the json is OK, we're forward compatible and will just ignore it
    isLenient = true
    // encoding nulls is meaningless and a waste of space.
    explicitNulls = false
    // adding new fields is OK even if older clients won't understand it
    ignoreUnknownKeys = true
    // ignore unknown enum values
    coerceInputValues = true
    // handle NaN and infinity
    allowSpecialFloatingPointValues = true
}

fun jsObject(init: dynamic.() -> Unit): dynamic {
    val o = js("{}")
    init.invoke(o)
    return o
}


fun <T> toJsObject(serializer: KSerializer<T>, obj: T): dynamic {
    val element = DEFAULT_JSON.encodeToJsonElement(serializer,obj).jsonObject
    return element.toJs()
}

fun <T> fromJsObject(serializer: KSerializer<T>, jsObject: dynamic): T {
    val jsonString = JSON.stringify(jsObject)
    return DEFAULT_JSON.decodeFromString(serializer, jsonString)
}

fun JsonArray.toJs(): dynamic {
    val array: dynamic = js("([])")
    forEach { value ->
        val jsValue = when(value) {
            is JsonObject -> value.toJs()
            is JsonArray -> value.toJs()
            is JsonPrimitive -> value.toJs()
            is JsonNull -> null
        }
        @Suppress("UnsafeCastFromDynamic")
        if(jsValue != null) {
            array.push(jsValue)
        }
    }
    return array
}

fun JsonObject.toJs(): dynamic {
    val original=this
    val o = jsObject {
        original.entries.forEach {(key,value) ->
            val jsValue = when(value) {
                is JsonObject -> value.toJs()
                is JsonArray -> value.toJs()
                is JsonPrimitive -> value.toJs()
                is JsonNull -> null
            }
            if(jsValue != null) {
                this[key] = jsValue
            }
        }
    }
    return o
}

fun JsonPrimitive.toJs(): dynamic {
    return when {
        this.isString -> this.content
        this.booleanOrNull != null -> this.boolean
        this.intOrNull != null -> <http://this.int|this.int>
        this.doubleOrNull != null -> this.double
        else -> this.content
    }
}
r
Have you compared your
toJsObject()
implementation with simple
JSON.parse(DEFAULT_JSON.encodeToString(serializer, obj))
?
j
No, but I guess that would work too functionally. But the extra parse step seems a bit wasteful.
r
In the past I have used https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-dynamic.html, but I had some strange issues with it and decided to use JSON.parse method instead.
j
ah thanks, wasn't aware of that way
what kind of issues did you have with it?
Probably is doing something similar to what I'm doing here but perhaps a bit faster.
r
I have found my commit, but don't remember exactly what the problem was. It had something to do with encoding default values. https://github.com/rjaros/kvision/commit/abd5887b322129ea1d891888b27ff4bd0afa3808
j
Right, makes sense. I've tried out encodeToDynamic and seems to work as I want so far.