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 };
}
@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",
}
@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[] };
}
@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 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 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",
}
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.
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;
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;
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 };
}