Skip to main content

Maps

Primitive maps​

Maps with primitive keys and values will be converted to a mapped type.

@Serializable
data class Config(
val properties: Map<String, String>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(Config.serializer()))
}

You can get the full code here.

export interface Config {
properties: { [key: string]: string };
}

Enum keys​

@Serializable
class Application(
val settings: Map<SettingKeys, String>
)

@Serializable
enum class SettingKeys {
SCREEN_SIZE,
MAX_MEMORY,
}

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(Application.serializer()))
}

You can get the full code here.

export interface Application {
settings: { [key in SettingKeys]: string };
}

export enum SettingKeys {
SCREEN_SIZE = "SCREEN_SIZE",
MAX_MEMORY = "MAX_MEMORY",
}

Maps with Collections​

@Serializable
class MapsWithLists(
val mapOfLists: Map<String, List<String>>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(MapsWithLists.serializer()))
}

You can get the full code here.

export interface MapsWithLists {
mapOfLists: { [key: string]: string[] };
}

Maps with value classes​

@Serializable
@JvmInline
value class Data(val content: String)

@Serializable
class MyDataClass(
val mapOfLists: Map<String, Data>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(MyDataClass.serializer()))
}

You can get the full code here.

export interface MyDataClass {
mapOfLists: { [key: string]: Data };
}

export type Data = string;

Nullable keys and values​

Nullable keys are not allowed in mapped types, so Maps of this type are convert to an ES6 Map.

An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type

@Serializable
data class Config(
val nullableVals: Map<String, String?>,
val nullableKeys: Map<String?, String>,
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(Config.serializer()))
}

You can get the full code here.

export interface Config {
nullableVals: { [key: string]: string | null };
// [key: string | null] is not allowed
nullableKeys: Map<string | null, string>;
}

Type alias keys​

Type aliased keys should still use an indexed type, if the type alias is suitable.

@Serializable
data class Example(
val complex: Map<ComplexKey, String>,
val simple: Map<SimpleKey, String>,
val doubleSimple: Map<DoubleSimpleKey, String>,
val enum: Map<EnumKey, String>,
val doubleEnum: Map<DoubleEnumKey, String>,
)

@Serializable
data class ComplexKey(val complex: String)

@Serializable
@JvmInline
value class SimpleKey(val simple: String)

@Serializable
@JvmInline
value class DoubleSimpleKey(val simple: SimpleKey)

@Serializable
enum class ExampleEnum { A, B, C, }

@Serializable
@JvmInline
value class EnumKey(val e: ExampleEnum)

@Serializable
@JvmInline
value class DoubleEnumKey(val e: ExampleEnum)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(Example.serializer()))
}

You can get the full code here.

export interface Example {
complex: Map<ComplexKey, string>;
simple: { [key: SimpleKey]: string };
doubleSimple: { [key: DoubleSimpleKey]: string };
enum: { [key in EnumKey]: string };
doubleEnum: { [key in DoubleEnumKey]: string };
}

export interface ComplexKey {
complex: string;
}

export type SimpleKey = string;

export type DoubleSimpleKey = SimpleKey;

export type EnumKey = ExampleEnum;

export type DoubleEnumKey = ExampleEnum;

export enum ExampleEnum {
A = "A",
B = "B",
C = "C",
}

Maps with complex keys​

JSON maps must have keys that are either strings, positive integers, or enums.

See the Kotlinx Serialization docs .

As a workaround, maps with structured keys are generated as ES6 maps.

To produce correct JSON, either write a custom serializer or use an explicit map-key class.

ES6 Map​

This is the default behaviour of KxsTsGen when it encounters complex map keys.

KxsTsGen produces valid TypeScript, but the TypeScript might not produce correct JSON.

@Serializable
data class Colour(
val r: UByte,
val g: UByte,
val b: UByte,
val a: UByte,
)

@Serializable
data class CanvasProperties(
val colourNames: Map<Colour, String>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(CanvasProperties.serializer()))
}

You can get the full code here.

export interface CanvasProperties {
colourNames: Map<Colour, string>;
}

export interface Colour {
r: UByte;
g: UByte;
b: UByte;
a: UByte;
}

export type UByte = number;

Maps with complex keys - Map Key class​

This approach is less optimised, but more declarative and easier to understand than writing custom serializers.

Because the value class ColourMapKey has a single string value, the descriptor is a PrimitiveKind.STRING.

KxsTsGen will generate a JSON-safe mapped-type property.

@Serializable
data class Colour(
val r: UByte,
val g: UByte,
val b: UByte,
val a: UByte,
)

/**
* Encode a [Colour] as an 8-character string
*
* Red, green, blue, and alpha are encoded as base-16 strings.
*/
@Serializable
@JvmInline
value class ColourMapKey(private val rgba: String) {
constructor(colour: Colour) : this(
listOf(
colour.r,
colour.g,
colour.b,
colour.a,
).joinToString("") {
it.toString(16).padStart(2, '0')
}
)

fun toColour(): Colour {
val (r, g, b, a) = rgba.chunked(2).map { it.toUByte(16) }
return Colour(r, g, b, a)
}
}

@Serializable
data class CanvasProperties(
val colourNames: Map<ColourMapKey, String>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(CanvasProperties.serializer()))
}

You can get the full code here.

Because the map now has a non-complex key, an 'indexed type' is generated.

export interface CanvasProperties {
colourNames: { [key: ColourMapKey]: string };
}

export type ColourMapKey = string;

Maps with complex keys - custom serializer workaround​

Define a custom serializer for Colour that will encode and decode to/from a string.

When encoding or decoding values with Kotlinx Serialization, under the hood it will create suitable map keys.

Because the custom serializer is a PrimitiveKind.STRING, KxsTsGen will generate a JSON-safe mapped-type property.

import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*

@Serializable(with = ColourAsStringSerializer::class)
data class Colour(
val r: UByte,
val g: UByte,
val b: UByte,
val a: UByte,
)

/**
* Encode a [Colour] as an 8-character string
*
* Red, green, blue, and alpha are encoded as base-16 strings.
*/
object ColourAsStringSerializer : KSerializer<Colour> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Colour", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: Colour) {
encoder.encodeString(
listOf(
value.r,
value.g,
value.b,
value.a,
).joinToString("") {
it.toString(16).padStart(2, '0')
}
)
}

override fun deserialize(decoder: Decoder): Colour {
val string = decoder.decodeString()
val (r, g, b, a) = string.chunked(2).map { it.toUByte(16) }
return Colour(r, g, b, a)
}
}

@Serializable
data class CanvasProperties(
val colourNames: Map<Colour, String>
)

fun main() {
val tsGenerator = KxsTsGenerator()
println(tsGenerator.generate(CanvasProperties.serializer()))
}

You can get the full code here.

export interface CanvasProperties {
colourNames: { [key: string]: string };
}