Let's say I have the following architecture:
On the TypeScript extension code I invoke a @JsExport
-ed service function:
async refreshWebApp(): void {
const data = await kotlinService.getData(); // returns Promise<Data>
// TODO: send data to web app
}
Where data
is:
// commonMain
@JsExport
class Data(val name: String, val age: Int, val addresses: List<String>)
How would I now send the data
instance to the web app? I cannot simply do:
webview.postMessage(data); // Does not work!
because the returned Kotlin object uses non-serializable accessors and Kotlin-specific classes (KtList
in this case).
Well, the only way to provide a pleasant experience to our TS consumers is to also add a mapping layer.
We provide a new TS type, with accompaining mapping functions:
export type JsData = {
readonly name: string;
readonly age: number;
readonly addresses: string[];
};
export function ktDataToJsData(value: Data): JsData {
return {
name: value.name,
age: value.age,
addresses: value.addresses.asJsReadonlyArrayView().slice(),
};
}
export function jsDataToKtData(value: JsData): Data {
return new Data(value.name, value.age, KtList.fromJsArray(value.addresses));
}
The above example then becomes:
async refreshWebApp(): void {
const data = await kotlinService.getData();
const jsData = ktDataToJsData(data); // Our manually coded mapping function
webview.postMessage(jsData);
}
But imagine having to do this for dozens of exported Kotlin classes, it's difficult to justify in terms of time and maintainability.
Much like what @JsPlainObject
does to an external interface
, a new @JsSerializable
annotation applied
to an exported Kotlin class could contribute static mapping functions via a companion object.
In practice this would mean scrapping the manually coded type
and function
(s).
Simply add the annotation to the Kotlin class:
// commonMain
@JsExport
@JsSerializable
class Data(val name: String, val age: Int, val addresses: List<String>)
And again, the above example then becomes:
async refreshWebApp(): void {
const data = await kotlinService.getData();
const jsData = Data.toJso(data); // Mapping function generated by the compiler plugin
webview.postMessage(jsData); // jsData is an interface JsoData, generated and exported by the compiler plugin
}
The same concept also applies to the trip back from the Web App to the VS Code extension:
async onWebAppMessage(message: JsoData): void {
const data = Data.fromJso(message); // Mapping function generated by the compiler plugin
await kotlinService.persistData(data);
}