Skip to content

Commit 48f5d24

Browse files
committed
docs, more Maps tests/examples/workarounds
1 parent dc1fd81 commit 48f5d24

11 files changed

+400
-16
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Kotlinx Serialization TypeScript Generator
22

3-
Create TypeScript interfaces from Kotlin classes
3+
Create TypeScript interfaces from Kotlinx Serialization classes.
44

55
```kotlin
66
@Serializable
@@ -21,4 +21,23 @@ interface PlayerDetails {
2121
}
2222
```
2323

24+
The aim is to create TypeScript interfaces that can accurately produce JSON that Kotlinx
25+
Serialization can parse.
26+
2427
See [the docs](./docs) for working examples.
28+
29+
## Status
30+
31+
This is a proof-of-concept.
32+
33+
| | Status | Notes |
34+
|---------------------------------------|----------------------------------------------------------|:--------------------------------------------------------------------------------------------------|
35+
| Basic classes |[example](./docs/basic-classes.md) | |
36+
| Nullable and default-value properties |[example](./docs/default-values.md) | |
37+
| Value classes |[example](./docs/value-classes.md) | |
38+
| Enums |[example](./docs/enums.md) | |
39+
| Lists |[example](./docs/lists.md) | |
40+
| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map. [See](./docs/maps.md#maps-with-complex-keys) |
41+
| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored |
42+
| Polymorphism - Open classes |[example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` |
43+
| Edge cases - circular dependencies |[example](./docs/edgecases.md) | |

docs/default-values.md

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
* [Introduction](#introduction)
88
* [Default values](#default-values)
9+
* [Nullable values](#nullable-values)
910
* [Default and nullable](#default-and-nullable)
1011

1112
<!--- END -->
@@ -18,6 +19,7 @@ import dev.adamko.kxstsgen.*
1819

1920
## Introduction
2021

22+
Some properties of a class are optional, or nullable, or both.
2123

2224
### Default values
2325

@@ -26,32 +28,64 @@ it will be marked as optional using the `?:` notation.
2628

2729
```kotlin
2830
@Serializable
29-
class Color(val rgb: Int = 12345)
31+
class Colour(val rgb: Int = 12345)
3032

3133
fun main() {
3234
val tsGenerator = KxsTsGenerator()
33-
println(tsGenerator.generate(Color.serializer()))
35+
println(tsGenerator.generate(Colour.serializer()))
3436
}
3537
```
3638

3739
> You can get the full code [here](./knit/example/example-default-values-single-field-01.kt).
3840
3941
```typescript
40-
export interface Color {
42+
export interface Colour {
4143
rgb?: number;
4244
}
4345
```
4446

4547
<!--- TEST -->
4648

49+
### Nullable values
50+
51+
Properties might be required, but the value can be nullable. In TypeScript that is represented with
52+
a type union that includes `null`.
53+
54+
```kotlin
55+
@Serializable
56+
class Colour(val rgb: Int?) // 'rgb' is required, but the value can be null
57+
58+
fun main() {
59+
val tsGenerator = KxsTsGenerator()
60+
println(tsGenerator.generate(Colour.serializer()))
61+
}
62+
```
63+
64+
> You can get the full code [here](./knit/example/example-default-values-single-field-02.kt).
65+
66+
```typescript
67+
export interface Colour {
68+
rgb: number | null;
69+
}
70+
```
71+
72+
<!--- TEST -->
73+
4774
### Default and nullable
4875

76+
A property can be both nullable and optional, which gives four possible options.
77+
4978
```kotlin
5079
@Serializable
5180
data class ContactDetails(
81+
// nullable: ❌, optional: ❌
82+
val name: String,
83+
// nullable: ✅, optional: ❌
5284
val email: String?,
85+
// nullable: ❌, optional: ✅
86+
val active: Boolean = true,
87+
// nullable: ✅, optional: ✅
5388
val phoneNumber: String? = null,
54-
val active: Boolean? = true,
5589
)
5690

5791
fun main() {
@@ -62,15 +96,12 @@ fun main() {
6296

6397
> You can get the full code [here](./knit/example/example-default-values-primitive-fields-01.kt).
6498
65-
Email has no default, so it is not marked as optional.
66-
67-
Phone number and is nullable, and has a default, so i
68-
6999
```typescript
70100
export interface ContactDetails {
101+
name: string;
71102
email: string | null;
103+
active?: boolean;
72104
phoneNumber?: string | null;
73-
active?: boolean | null;
74105
}
75106
```
76107

docs/knit/example/example-default-values-primitive-fields-01.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import dev.adamko.kxstsgen.*
77

88
@Serializable
99
data class ContactDetails(
10+
// nullable: ❌, optional: ❌
11+
val name: String,
12+
// nullable: ✅, optional: ❌
1013
val email: String?,
14+
// nullable: ❌, optional: ✅
15+
val active: Boolean = true,
16+
// nullable: ✅, optional: ✅
1117
val phoneNumber: String? = null,
12-
val active: Boolean? = true,
1318
)
1419

1520
fun main() {

docs/knit/example/example-default-values-single-field-01.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import kotlinx.serialization.*
66
import dev.adamko.kxstsgen.*
77

88
@Serializable
9-
class Color(val rgb: Int = 12345)
9+
class Colour(val rgb: Int = 12345)
1010

1111
fun main() {
1212
val tsGenerator = KxsTsGenerator()
13-
println(tsGenerator.generate(Color.serializer()))
13+
println(tsGenerator.generate(Colour.serializer()))
1414
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This file was automatically generated from default-values.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
class Colour(val rgb: Int?) // 'rgb' is required, but the value can be null
10+
11+
fun main() {
12+
val tsGenerator = KxsTsGenerator()
13+
println(tsGenerator.generate(Colour.serializer()))
14+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// This file was automatically generated from maps.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleMapComplex02
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Colour(
10+
val r: UByte,
11+
val g: UByte,
12+
val b: UByte,
13+
val a: UByte,
14+
)
15+
16+
/**
17+
* Encode a [Colour] as an 8-character string
18+
*
19+
* Red, green, blue, and alpha are encoded as base-16 strings.
20+
*/
21+
@Serializable
22+
@JvmInline
23+
value class ColourMapKey(private val rgba: String) {
24+
constructor(colour: Colour) : this(
25+
listOf(
26+
colour.r,
27+
colour.g,
28+
colour.b,
29+
colour.a,
30+
).joinToString("") {
31+
it.toString(16).padStart(2, '0')
32+
}
33+
)
34+
35+
fun toColour(): Colour {
36+
val (r, g, b, a) = rgba.chunked(2).map { it.toUByte(16) }
37+
return Colour(r, g, b, a)
38+
}
39+
}
40+
41+
@Serializable
42+
data class CanvasProperties(
43+
val colourNames: Map<ColourMapKey, String>
44+
)
45+
46+
fun main() {
47+
val tsGenerator = KxsTsGenerator()
48+
println(tsGenerator.generate(CanvasProperties.serializer()))
49+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// This file was automatically generated from maps.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleMapComplex03
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
import kotlinx.serialization.descriptors.*
9+
import kotlinx.serialization.encoding.*
10+
11+
@Serializable(with = ColourAsStringSerializer::class)
12+
data class Colour(
13+
val r: UByte,
14+
val g: UByte,
15+
val b: UByte,
16+
val a: UByte,
17+
)
18+
19+
/**
20+
* Encode a [Colour] as an 8-character string
21+
*
22+
* Red, green, blue, and alpha are encoded as base-16 strings.
23+
*/
24+
object ColourAsStringSerializer : KSerializer<Colour> {
25+
override val descriptor: SerialDescriptor =
26+
PrimitiveSerialDescriptor("Colour", PrimitiveKind.STRING)
27+
28+
override fun serialize(encoder: Encoder, value: Colour) {
29+
encoder.encodeString(
30+
listOf(
31+
value.r,
32+
value.g,
33+
value.b,
34+
value.a,
35+
).joinToString("") {
36+
it.toString(16).padStart(2, '0')
37+
}
38+
)
39+
}
40+
41+
override fun deserialize(decoder: Decoder): Colour {
42+
val string = decoder.decodeString()
43+
val (r, g, b, a) = string.chunked(2).map { it.toUByte(16) }
44+
return Colour(r, g, b, a)
45+
}
46+
}
47+
48+
@Serializable
49+
data class CanvasProperties(
50+
val colourNames: Map<Colour, String>
51+
)
52+
53+
fun main() {
54+
val tsGenerator = KxsTsGenerator()
55+
println(tsGenerator.generate(CanvasProperties.serializer()))
56+
}

docs/knit/test/DefaultValuesTest.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,30 @@ class DefaultValuesTest {
1616
.shouldBe(
1717
// language=TypeScript
1818
"""
19-
|export interface Color {
19+
|export interface Colour {
2020
| rgb?: number;
2121
|}
2222
""".trimMargin()
2323
.normalize()
2424
)
2525
}
2626

27+
@Test
28+
fun testExampleDefaultValuesSingleField02() {
29+
captureOutput("ExampleDefaultValuesSingleField02") {
30+
dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02.main()
31+
}.normalizeJoin()
32+
.shouldBe(
33+
// language=TypeScript
34+
"""
35+
|export interface Colour {
36+
| rgb: number | null;
37+
|}
38+
""".trimMargin()
39+
.normalize()
40+
)
41+
}
42+
2743
@Test
2844
fun testExampleDefaultValuesPrimitiveFields01() {
2945
captureOutput("ExampleDefaultValuesPrimitiveFields01") {
@@ -33,9 +49,10 @@ class DefaultValuesTest {
3349
// language=TypeScript
3450
"""
3551
|export interface ContactDetails {
52+
| name: string;
3653
| email: string | null;
54+
| active?: boolean;
3755
| phoneNumber?: string | null;
38-
| active?: boolean | null;
3956
|}
4057
""".trimMargin()
4158
.normalize()

docs/knit/test/MapsTests.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,38 @@ class MapsTests {
8585
.normalize()
8686
)
8787
}
88+
89+
@Test
90+
fun testExampleMapComplex02() {
91+
captureOutput("ExampleMapComplex02") {
92+
dev.adamko.kxstsgen.example.exampleMapComplex02.main()
93+
}.normalizeJoin()
94+
.shouldBe(
95+
// language=TypeScript
96+
"""
97+
|export interface CanvasProperties {
98+
| colourNames: Map<ColourMapKey, string>;
99+
|}
100+
|
101+
|export type ColourMapKey = string;
102+
""".trimMargin()
103+
.normalize()
104+
)
105+
}
106+
107+
@Test
108+
fun testExampleMapComplex03() {
109+
captureOutput("ExampleMapComplex03") {
110+
dev.adamko.kxstsgen.example.exampleMapComplex03.main()
111+
}.normalizeJoin()
112+
.shouldBe(
113+
// language=TypeScript
114+
"""
115+
|export interface CanvasProperties {
116+
| colourNames: { [key: string]: string };
117+
|}
118+
""".trimMargin()
119+
.normalize()
120+
)
121+
}
88122
}

0 commit comments

Comments
 (0)