kotlin-to-ts-ts
purpose
Rewriting Kotlin source code as idiomatic TypeScript — covering Kotlin-specific syntax (trailing lambdas, null-safety operators, string templates, it, when, extension functions) that the Java-to-TS expert does not address.
rules
- Kotlin string templates (
"Hello $name","text ${expr}") map directly to TypeScript template literals (`Hello ${name}`,`text ${expr}`). Multi-line strings with.trimIndent()become TS template literals with no call needed — TS template literals already preserve literal indentation. - Kotlin trailing lambda syntax (
app.command("/echo") { req, ctx -> ... }) maps to a callback argument:app.command("/echo", async (req, ctx) => { ... }). The lambda body{ ... }becomesasync (...) => { ... }when the target API is async. - Kotlin SAM (Single Abstract Method) conversions — where a lambda replaces a single-method interface — map to TypeScript function arguments directly.
app.event(handler)wherehandleris a lambda becomesapp.on("event", async (ctx) => { ... }). - Kotlin
it(implicit single-parameter lambda) must be given an explicit name in TS.list.filter { it.isActive }becomeslist.filter((item) => item.isActive). Choose a meaningful name from context (req,ctx,msg,user, etc.). - Kotlin null-safety operator
?.maps to TS optional chaining?.. Kotlin!!(non-null assertion) maps to TS!(non-null assertion). Kotlin elvis?:maps to TS nullish coalescing??. Examples:user?.name→user?.name,value!!→value!,name ?: "default"→name ?? "default". - Kotlin
whenexpressions map to TSswitchstatements or chained ternaries.whenwith no subject (boolean conditions) maps toif/else if.whenwith a subject (value matching) maps toswitch. If used as an expression (assigned to a variable), prefer chained ternaries or an IIFE wrapping aswitch. - Kotlin
valmaps toconst(for locals) orreadonly(for class fields). Kotlinvarmaps tolet. Never usevarin the TS output — alwaysconstorlet. - Kotlin
funat package level (top-level functions) maps to TS exported functions:export function myFn() { ... }. Kotlin does not require a wrapping class for top-level functions, and neither does TS. - Kotlin extension functions (
fun String.toSlug(): String) have no direct TS equivalent. Convert to a standalone utility function:function toSlug(s: string): string. If the extension is on a project type, consider adding a method to the class instead. - Kotlin
data classmaps to a TypeScriptinterface(for pure data) or aclasswith constructor shorthand (if methods are needed).data class User(val name: String, val email: String)→interface User { readonly name: string; readonly email: string; }. Destructuringval (name, email) = user→const { name, email } = user. - Kotlin
objectdeclarations (singletons) map to a plain TS module-levelconstobject or a namespace.object Config { val port = 3000 }→const Config = { port: 3000 } as const. Kotlincompanion objectmaps tostaticmembers on the class or module-level constants. - Kotlin
sealed class/sealed interfacemaps to TS discriminated union types.sealed class Resultwith subclassesSuccessandError→type Result = { kind: 'success'; value: T } | { kind: 'error'; error: string }. - Kotlin scope functions (
let,run,apply,also,with) should be inlined rather than translated literally.user?.let { sendEmail(it) }→if (user) sendEmail(user).config.apply { port = 3000; host = "localhost" }→ direct property assignments. - Kotlin
listOf(),mapOf(),mutableListOf(),mutableMapOf()map to TS array/object literals:listOf("a", "b")→["a", "b"],mapOf("key" to "value")→{ key: "value" }ornew Map([["key", "value"]]). - Kotlin
for (item in list)maps tofor (const item of list). Kotlin rangesfor (i in 0 until n)→for (let i = 0; i < n; i++). Kotlinfor (i in 0..n)(inclusive) →for (let i = 0; i <= n; i++). - Kotlin type casts:
as(unsafe cast) → TSas(type assertion).as?(safe cast) → TS has no direct equivalent; use a type guard function or conditional check. - Kotlin
::class.java/SomeClass::class.java(class references for reflection) should be removed. In the Slack Bolt SDK,app.event(AppMentionEvent::class.java) { ... }becomes a string-based route:app.on("message", async (ctx) => { ... })in Teams.
patterns
Trailing lambda + ack pattern (Slack Bolt → Teams)
// --- Before (Kotlin) ---
app.command("/echo") { req, ctx ->
val text = "You said ${req.payload.text} at <#${req.payload.channelId}|${req.payload.channelName}>"
ctx.respond { it.text(text) }
ctx.ack()
}// --- After (TypeScript, Teams SDK) ---
app.on('message', async ({ activity, send }) => {
const text = `You said ${activity.text}`;
await send(text);
});Null-safety chain
// --- Before (Kotlin) ---
val hash = event.event.view?.hash
val name = user?.profile?.displayName ?: "Unknown"
val id = data!!.userId// --- After (TypeScript) ---
const hash = event.event.view?.hash;
const name = user?.profile?.displayName ?? 'Unknown';
const id = data!.userId;String templates + trimIndent
// --- Before (Kotlin) ---
val view = """
{
"type": "home",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Hello ${user.name}! Updated: ${ZonedDateTime.now()}"
}
}
]
}
""".trimIndent()// --- After (TypeScript) ---
const view = {
type: 'home' as const,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `Hello ${user.name}! Updated: ${new Date().toISOString()}`,
},
},
],
};
// Prefer a typed object over a JSON string when the target SDK accepts objects.
// If a raw string is truly needed:
const viewJson = JSON.stringify(view);When expression → switch
// --- Before (Kotlin) ---
val response = when (action) {
"approve" -> "Approved!"
"reject" -> "Rejected."
"defer" -> "Deferred to next week."
else -> "Unknown action: $action"
}// --- After (TypeScript) ---
let response: string;
switch (action) {
case 'approve':
response = 'Approved!';
break;
case 'reject':
response = 'Rejected.';
break;
case 'defer':
response = 'Deferred to next week.';
break;
default:
response = `Unknown action: ${action}`;
}Scope function inlining
// --- Before (Kotlin) ---
val result = config.apply {
port = 3000
host = "localhost"
}
user?.let { ctx.say("Hello ${it.name}") }
val mapped = items.map { it.name to it.value }.toMap()// --- After (TypeScript) ---
const config = { port: 3000, host: 'localhost' };
if (user) {
await send(`Hello ${user.name}`);
}
const mapped = Object.fromEntries(items.map((item) => [item.name, item.value]));Object declaration / companion object
// --- Before (Kotlin) ---
class ResourceLoader {
companion object {
fun loadAppConfig(name: String = "appConfig.json"): AppConfig {
// ...
}
}
}
// Usage: ResourceLoader.loadAppConfig()// --- After (TypeScript) ---
// Companion object → module-level function (no class wrapper needed)
export function loadAppConfig(name = 'appConfig.json'): AppConfig {
// ...
}
// Usage: loadAppConfig()pitfalls
- Forgetting to name
it: Every Kotlinitreference must get an explicit TS parameter name. Blindly searching foritwill produce false positives on English words — search for{ it.and{ it ->patterns. trimIndent()on JSON strings: Kotlin examples often build JSON as.trimIndent()multiline strings. In TS, prefer a typed object literal instead of a string. If the target API needs a string, useJSON.stringify(obj)for safety over manual template literals.!!overuse: Kotlin's!!means "throw if null". TS's!is only a compile-time assertion — it does NOT throw at runtime. If the Kotlin code relies on!!for runtime safety, add an explicit null check instead.as?safe cast: Kotlin'sas?returnsnullif the cast fails. TS'sasnever fails at runtime (it's a compile-time assertion). Translateas?to a type guard check, not a bareas.- Trailing lambda position: Kotlin allows the last lambda argument to be outside the parentheses. In TS, ALL arguments go inside the parentheses.
app.command("/echo") { req, ctx -> }→app.command("/echo", async (req, ctx) => { }). listOf()/mapOf()immutability: Kotlin'slistOf()returns an immutable list. TS arrays are mutable by default. If immutability matters, useas constorReadonlyArray<T>.- Class reference syntax:
SomeClass::class.javain Kotlin (used for event type registration in Slack Bolt) has no TS equivalent. Replace with the string event name expected by the target SDK. - Extension functions on primitives: Kotlin can extend
String,Int, etc. TS cannot extend primitive types. Always convert to standalone functions. - Destructuring data classes: Kotlin
val (a, b) = pairusescomponentN()functions. TS destructuring uses property names:const { first, second } = pair. The names must match.
references
- https://kotlinlang.org/docs/basic-syntax.html — Kotlin syntax reference
- https://kotlinlang.org/docs/null-safety.html — Kotlin null-safety operators
- https://kotlinlang.org/docs/lambdas.html — Kotlin lambda syntax and SAM conversions
- https://kotlinlang.org/docs/scope-functions.html — let, run, apply, also, with
- https://kotlinlang.org/docs/data-classes.html — Data classes
- https://kotlinlang.org/docs/extensions.html — Extension functions
- https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html — TS template literals
- https://www.typescriptlang.org/docs/handbook/2/narrowing.html — TS type narrowing and guards
instructions
Use this expert when the source code is Kotlin (.kt files). It handles Kotlin-specific syntax that the java-to-ts-ts.md expert does not cover: trailing lambdas, it implicit parameters, string templates, trimIndent(), null-safety operators (?., !!, ?:), when expressions, scope functions, extension functions, data class, object/companion object, sealed classes, and ::class.java references. For Java SDK types, generics, collections, and Lombok patterns, pair with java-to-ts-ts.md. For type system mapping, pair with type-mapping-ts.md.
research
Deep Research prompt:
"Write a micro expert on converting Kotlin to TypeScript. Cover: string templates to template literals, trailing lambda syntax to callback arguments, SAM conversions, it implicit parameter, null-safety operators (?. !! ?:) to optional chaining/nullish coalescing/non-null assertion, when expressions to switch, val/var to const/let, extension functions to utility functions, data class to interface, object declarations to module constants, sealed class to discriminated unions, scope functions (let/run/apply/also/with) inlining, and class reference syntax removal. Include 4-5 worked side-by-side examples."