CLOVIS
05/06/2025, 3:59 PM@JsExport
@Serializable
data class Foo(
val bar: List<String>
)
is not exportable because List
is not exportable.
@JsExport
@Serializable
data class Foo(
val bar: Array<String>
)
doesn't work, because array equality is identity-based and not content-based.
@JsExport
@Serializable
class Foo(
val bar: Array<String>
) {
override fun equals(…) = …
override fun hashCode() = …
}
works, but it's a shame to have to generate equals
and hashCode
on all objects when one of the big advantages of Kotlin is not having to do this, especially for DTOs.
@JsExport
@Serializable
class MyCustomArray<T>(
val data: Array<T>
) { … }
@JsExport
@Serializable
class Foo(
val bar: MyCustomArray<T>,
)
doesn't work because KotlinX.Serializable can't serialize generic arrays.
@JsExport
@Serializable
class MyCustomList<T> private constructor(
internal val data: List<T>
) {
val elements: Array<T>
get() = ⚠
}
@JsExport
@Serializable
class Foo(
val bar: MyCustomList<T>
)
doesn't work because we can't convert a generic list into a generic array.
I'm curious, how do people share DTOs with JS? I see that the other solution is creating a dedicated JS shim, but the entire point of sharing code is to avoid duplication.Artem Kobzar
05/06/2025, 4:15 PMList
is exportable since 2.0Artem Kobzar
05/06/2025, 4:17 PMUmesh Solanki
05/06/2025, 4:17 PMEdoardo Luppi
05/06/2025, 4:39 PMhow do people share DTOs with JSI have a HUGE amount of mapping code in the TS side. That's it really.
Edoardo Luppi
05/06/2025, 4:39 PMEdoardo Luppi
05/06/2025, 4:45 PM@JsField
annotation, to avoid wrapping properties into accessors.turansky
05/06/2025, 9:50 PMJSON.stringify
2. Bad as payload for messaging (Window <-> Window, Window <-> Worker)
3. Bad for messaging with other JS applicationturansky
05/06/2025, 9:53 PMturansky
05/06/2025, 9:55 PMkotlinx-serialization
)turansky
05/06/2025, 9:56 PMCLOVIS
05/07/2025, 7:45 AMOh, that could solve all our problems then… I'll take a lookis exportable since 2.0List
CLOVIS
05/07/2025, 7:45 AMCLOVIS
05/07/2025, 7:48 AMCLOVIS
05/07/2025, 8:06 AMJsExport
should probably be updated to mention List
/`Set` /`Map`, it currently only mentions Array
:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.js/-js-export/turansky
05/07/2025, 9:42 AMList
, Set
, Map
- fine if Kotlin/JS is producer and doesn't receive data back.
In my previous cases communication was bidirectional and both parts (JS and Kotlin/JS) modified data.
In common case it means that data, which you provide should be convertable to JSON or cloneable (depends on your case).Artem Kobzar
05/07/2025, 9:50 AMArtem Kobzar
05/07/2025, 9:52 AMList
, Set
, Map
- fine if Kotlin/JS is producer and doesn't receive data back.
Why? Since 2.0 you can also create Kotlin List/Set/Map from JavaScript Array/Set/MapArtem Kobzar
05/07/2025, 9:53 AMThe documentation ofYes, it's true. However we want firstly to finish all the planned work on the @JsExport and fully update the documentation about JsExportshould probably be updated...JsExport
Edoardo Luppi
05/07/2025, 9:54 AMCLOVIS
05/07/2025, 10:06 AMAre they not exportable? I've just checked in the playground and it doesn't say they are notThe documentation of
@JsExport
says they are notCLOVIS
05/07/2025, 10:07 AMEdoardo Luppi
05/07/2025, 10:07 AMEdoardo Luppi
05/07/2025, 10:36 AMCLOVIS
05/07/2025, 12:04 PMEdoardo Luppi
05/07/2025, 12:11 PMEdoardo Luppi
05/07/2025, 12:12 PMEdoardo Luppi
05/07/2025, 12:14 PMHildebrandt Tobias
05/07/2025, 12:47 PMturansky
05/07/2025, 12:54 PMHildebrandt Tobias
05/07/2025, 12:54 PMexternal interface
and jso
turansky
05/07/2025, 12:57 PM@JsPlainObject
, created by @Artem Kobzar to avoid unsafe jso
calls 😉turansky
05/07/2025, 12:57 PMturansky
05/07/2025, 12:58 PMHildebrandt Tobias
05/07/2025, 12:58 PMturansky
05/07/2025, 1:00 PMtoJSON
will be generated for JS platform only (if you need it)CLOVIS
05/07/2025, 1:36 PMEdoardo Luppi
05/07/2025, 1:43 PMand have dedicated classes on the JS side anywayNo that shoud not be the case, not always at least. At the moment, we can definitely export Kotlin DTOs to JS, be them data classes or not, and have a JS consumer use them, and even create new instances. The problem arises when the exported DTOs need to be serialized and deserialized (e.g. to go through a messaging system, like
postMessage
BroadcastChannel
). In this specific case, you need "mapping" plain objects on the JS side, as the exported Kotlin DTOs cannot correctly serialize.turansky
05/07/2025, 1:59 PMKotlin DTOs cannot correctly serializeAnd regular JS classes have definitely the same problem
Edoardo Luppi
05/07/2025, 2:02 PMAnd regular JS classes have definitely the same problemIf they use accessors, yes. If they use regular fields, no.
Adam S
05/07/2025, 2:22 PMturansky
05/07/2025, 2:26 PMCLOVIS
05/07/2025, 2:36 PMThe problem arises when the exported DTOs need to be serialized and deserialized (e.g. to go through a messaging system, likeAh, you mean serialize using the native JS serialization? Because in my case, serialization is done through KotlinX.Serialization.postMessage
). In this specific case, you need "mapping" plain objects on the JS side, as the exported Kotlin DTOs cannot correctly serialize.BroadcastChannel
CLOVIS
05/07/2025, 2:37 PMencodeToDynamic
and decodeFromDynamic
. Wouldn't that solve the native JS serialization issues?turansky
05/07/2025, 2:39 PMturansky
05/07/2025, 2:40 PMCLOVIS
05/07/2025, 2:41 PMencodeToDynamic
does?turansky
05/07/2025, 2:45 PM// Kotlin common
// For commutnication in Kotlin
@Serializable
data class UserDTO(
val name: String,
val addresses: List<String>,
)
// Kotlin/JS/wasmJS
// For messaging
@JsPlainObject
external interface User {
val name: String
val addresses: ReadonlyArray<String>,
}
// TS
type User = Readonly<{
name: string
addresses: readonly string[]
}>
// usage
fun main() {
val user = UserDTO(...)
sendToJS(encodeToDynamic(user) /* User */)
}
turansky
05/07/2025, 2:47 PMIsn't that already whatResult can be non-serializable in JS termsdoes?encodeToDynamic
CLOVIS
05/07/2025, 2:47 PMturansky
05/07/2025, 2:55 PMCLOVIS
05/07/2025, 2:59 PMturansky
05/07/2025, 2:59 PMturansky
05/07/2025, 3:00 PMEdoardo Luppi
05/07/2025, 3:02 PMAlso, KotlinX.Serialization hasYes, but using them outside of the Kotlin realm is mission impossible at the moment. Assume you're in TS, and you have an instance of a Kotlin DTO, which you now have to serialize throughandencodeToDynamic
. Wouldn't that solve the native JS serialization issues?decodeFromDynamic
postMessage
, how are you going to do it? It wouldn't be a problem if the DTO properties were exposed as fields, but the are always wrapped currently.CLOVIS
05/07/2025, 3:03 PMCLOVIS
05/07/2025, 3:04 PMMyDto.serialize(): dynamic = encodeToDynamic(this)
and back seems like a lot less work than creating JsPlainObject duplicates of all classes + mapping functions, no?Edoardo Luppi
05/07/2025, 3:07 PMCLOVIS
05/07/2025, 3:30 PM@JsPlainObject
duplicate + Kotlin functions to convert back/from the DTO objects is any less workEdoardo Luppi
05/07/2025, 3:32 PMEdoardo Luppi
05/07/2025, 3:33 PMKClass
turansky
05/07/2025, 3:35 PMencode/decode
operations you do only inside Kotlin/JS applicationturansky
05/07/2025, 3:35 PMEdoardo Luppi
05/07/2025, 3:37 PMinterface
per class
to represent the plain object?
Plus it becomes a pain as now you're specializing the JS side, while the KMP mantra should be keeping as much as possible in commomMain
turansky
05/07/2025, 3:38 PM// Common
@Serializable
data class UserDTO
// JS only
@JsPlainObject
external interface UserJSO
Edoardo Luppi
05/07/2025, 3:40 PMjsMain
to complement the interfaces.
In my case I export classes and functions from commonMain
, and trying to do stuff under jsMain
would increase complexity quite a lot.Edoardo Luppi
05/07/2025, 3:44 PM// commonMain
@JsExport
class DtoProducer {
fun produce(): Dto = ...
}
@JsExport
class Dto(...)
Even if I have an interface such as
// jsMain
@JsPlainObject
external interface DtoPlain
What do I do with DtoProducer
?Edoardo Luppi
05/07/2025, 3:46 PMjsMain
, but that's me.
Proposing such a thing in a team (10-15 people) would be impossible as now everyone has to be familiar with the workaround mechanism.turansky
05/07/2025, 3:49 PMCLOVIS
05/07/2025, 4:05 PMCLOVIS
05/07/2025, 4:06 PMArtem Kobzar
05/07/2025, 4:15 PM@JsExport
@Serializable
data class Foo(
val bar: List<String>
)