Skip to content

Commit 3438872

Browse files
authored
Refine implementation (#14)
- workaround for json content polymorphic + test - fix element ID name extraction (handle other SerialDescriptors that have < > in them) - fix knit tests not running - fix generic test #11
1 parent 8c3491a commit 3438872

18 files changed

+480
-58
lines changed

README.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
# Kotlinx Serialization TypeScript Generator
44

5-
Create TypeScript interfaces from Kotlinx Serialization classes.
5+
[Kotlinx Serialization TypeScript Generator](https://github.com/adamko-dev/kotlinx-serialization-typescript-generator)
6+
creates TypeScript interfaces from
7+
[Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization/)
8+
classes.
69

710
```kotlin
811
@Serializable
@@ -39,23 +42,31 @@ The Kotlinx Serialization API should be used to generate TypeScript. The
3942
[`SerialDescriptor`s](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html)
4043
are flexible and comprehensive enough to allow for accurate TypeScript code, without any deviation.
4144

45+
The aim is to create TypeScript interfaces that can accurately produce Kotlinx Serialization
46+
compatible JSON.
47+
48+
The Kotlinx Serialization API should be used to generate TypeScript. The
49+
[`SerialDescriptor`s](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html)
50+
are flexible and comprehensive enough to allow for accurate TypeScript code, without any deviation.
51+
4252
See [the docs](./docs) for working examples.
4353

4454
## Status
4555

4656
This is a proof-of-concept.
4757

48-
| | Status | Notes |
49-
|---------------------------------------|----------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
50-
| Kotlin multiplatform || The codebase is multiplatform, but only JVM has been tested |
51-
| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript |
52-
| Basic classes |[example](./docs/basic-classes.md) | |
53-
| Nullable and default-value properties |[example](./docs/default-values.md) | |
54-
| Value classes |[example](./docs/value-classes.md) | |
55-
| Enums |[example](./docs/enums.md) | |
56-
| Lists |[example](./docs/lists.md) | |
57-
| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see](./docs/maps.md#maps-with-complex-keys) |
58-
| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see](./docs/polymorphism.md#nested-sealed-classes) |
59-
| Polymorphism - Open classes |[example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` |
60-
| `@JsonClassDiscriminator` || Not implemented |
61-
| Edge cases - circular dependencies |[example](./docs/edgecases.md) | |
58+
| | Status | Notes |
59+
|---------------------------------------|-----------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------|
60+
| Kotlin multiplatform || The codebase is multiplatform, but only JVM has been tested |
61+
| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript |
62+
| Basic classes |[example](./docs/basic-classes.md) | |
63+
| Nullable and default-value properties |[example](./docs/default-values.md) | |
64+
| Value classes |[example](./docs/value-classes.md) | |
65+
| Enums |[example](./docs/enums.md) | |
66+
| Lists |[example](./docs/lists.md) | |
67+
| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see documentation](./docs/maps.md#maps-with-complex-keys) |
68+
| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see documentation](./docs/polymorphism.md#nested-sealed-classes) |
69+
| Polymorphism - Open classes |[example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` |
70+
| `@JsonClassDiscriminator` || Not implemented |
71+
| JSON Content polymorphism |[example](./docs/polymorphism.md#json-content-polymorphism) | Not implemented. Converted to `type MyClass = any` |
72+
| Edge cases - circular dependencies |[example](./docs/edgecases.md) | |

docs/code/example/example-generics-01.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import dev.adamko.kxstsgen.*
88
import kotlinx.serialization.builtins.serializer
99

1010
@Serializable
11-
class Box<T>(
11+
class Box<T : Number>(
1212
val value: T,
1313
)
1414

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// This file was automatically generated from polymorphism.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleJsonPolymorphic01
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
import kotlinx.serialization.json.*
9+
10+
@Serializable
11+
abstract class Project {
12+
abstract val name: String
13+
}
14+
15+
@Serializable
16+
data class BasicProject(override val name: String) : Project()
17+
18+
@Serializable
19+
data class OwnedProject(override val name: String, val owner: String) : Project()
20+
21+
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
22+
override fun selectDeserializer(element: JsonElement) = when {
23+
"owner" in element.jsonObject -> OwnedProject.serializer()
24+
else -> BasicProject.serializer()
25+
}
26+
}
27+
28+
fun main() {
29+
val tsGenerator = KxsTsGenerator()
30+
println(tsGenerator.generate(ProjectSerializer))
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This file was automatically generated from lists.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleListObjects01
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Colour(
10+
val rgb: String
11+
)
12+
13+
@Serializable
14+
data class MyLists(
15+
val colours: List<Colour>,
16+
val colourGroups: Set<List<Colour>>,
17+
val colourGroupGroups: LinkedHashSet<List<List<Colour>>>,
18+
)
19+
20+
fun main() {
21+
val tsGenerator = KxsTsGenerator()
22+
println(tsGenerator.generate(MyLists.serializer()))
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This file was automatically generated from lists.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleListObjects02
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Colour(
10+
val rgb: String
11+
)
12+
13+
@Serializable
14+
data class MyLists(
15+
val listOfMaps: List<Map<String, Int>>,
16+
val listOfColourMaps: List<Map<String, Colour>>,
17+
)
18+
19+
fun main() {
20+
val tsGenerator = KxsTsGenerator()
21+
println(tsGenerator.generate(MyLists.serializer()))
22+
}

docs/code/example/example-list-primitive-01.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import dev.adamko.kxstsgen.*
88
@Serializable
99
data class MyLists(
1010
val strings: List<String>,
11-
val ints: List<Int>,
12-
val longs: List<Long>,
11+
val ints: Set<Int>,
12+
val longs: Collection<Long>,
1313
)
1414

1515
fun main() {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This file was automatically generated from lists.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleListPrimitive02
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Colour(
10+
val rgb: String
11+
)
12+
13+
@Serializable
14+
data class MyLists(
15+
val colours: List<Colour>,
16+
val colourGroups: List<List<Colour>>,
17+
val colourGroupGroups: List<List<List<Colour>>>,
18+
)
19+
20+
fun main() {
21+
val tsGenerator = KxsTsGenerator()
22+
println(tsGenerator.generate(MyLists.serializer()))
23+
}

docs/code/example/example-map-primitive-03.kt

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

88
@Serializable
9-
data class Config(
10-
val properties: Map<String?, String?>
9+
class MapsWithLists(
10+
val mapOfLists: Map<String, List<String>>
1111
)
1212

1313
fun main() {
1414
val tsGenerator = KxsTsGenerator()
15-
println(tsGenerator.generate(Config.serializer()))
15+
println(tsGenerator.generate(MapsWithLists.serializer()))
1616
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.exampleMapPrimitive04
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
@JvmInline
10+
value class Data(val content: String)
11+
12+
@Serializable
13+
class MyDataClass(
14+
val mapOfLists: Map<String, Data>
15+
)
16+
17+
fun main() {
18+
val tsGenerator = KxsTsGenerator()
19+
println(tsGenerator.generate(MyDataClass.serializer()))
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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.exampleMapPrimitive05
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Config(
10+
val properties: Map<String?, String?>
11+
)
12+
13+
fun main() {
14+
val tsGenerator = KxsTsGenerator()
15+
println(tsGenerator.generate(Config.serializer()))
16+
}

docs/code/test/ListsTests.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,47 @@ class ListsTests {
2525
.normalize()
2626
)
2727
}
28+
29+
@Test
30+
fun testExampleListObjects01() {
31+
captureOutput("ExampleListObjects01") {
32+
dev.adamko.kxstsgen.example.exampleListObjects01.main()
33+
}.normalizeJoin()
34+
.shouldBe(
35+
// language=TypeScript
36+
"""
37+
|export interface MyLists {
38+
| colours: Colour[];
39+
| colourGroups: Colour[][];
40+
| colourGroupGroups: Colour[][][];
41+
|}
42+
|
43+
|export interface Colour {
44+
| rgb: string;
45+
|}
46+
""".trimMargin()
47+
.normalize()
48+
)
49+
}
50+
51+
@Test
52+
fun testExampleListObjects02() {
53+
captureOutput("ExampleListObjects02") {
54+
dev.adamko.kxstsgen.example.exampleListObjects02.main()
55+
}.normalizeJoin()
56+
.shouldBe(
57+
// language=TypeScript
58+
"""
59+
|export interface MyLists {
60+
| listOfMaps: { [key: string]: number }[];
61+
| listOfColourMaps: { [key: string]: Colour }[];
62+
|}
63+
|
64+
|export interface Colour {
65+
| rgb: string;
66+
|}
67+
""".trimMargin()
68+
.normalize()
69+
)
70+
}
2871
}

docs/code/test/MapsTests.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,40 @@ class MapsTests {
4949
fun testExampleMapPrimitive03() {
5050
captureOutput("ExampleMapPrimitive03") {
5151
dev.adamko.kxstsgen.example.exampleMapPrimitive03.main()
52+
}.normalizeJoin()
53+
.shouldBe(
54+
// language=TypeScript
55+
"""
56+
|export interface MapsWithLists {
57+
| mapOfLists: { [key: string]: string[] };
58+
|}
59+
""".trimMargin()
60+
.normalize()
61+
)
62+
}
63+
64+
@Test
65+
fun testExampleMapPrimitive04() {
66+
captureOutput("ExampleMapPrimitive04") {
67+
dev.adamko.kxstsgen.example.exampleMapPrimitive04.main()
68+
}.normalizeJoin()
69+
.shouldBe(
70+
// language=TypeScript
71+
"""
72+
|export interface MyDataClass {
73+
| mapOfLists: { [key: string]: Data };
74+
|}
75+
|
76+
|export type Data = string;
77+
""".trimMargin()
78+
.normalize()
79+
)
80+
}
81+
82+
@Test
83+
fun testExampleMapPrimitive05() {
84+
captureOutput("ExampleMapPrimitive05") {
85+
dev.adamko.kxstsgen.example.exampleMapPrimitive05.main()
5286
}.normalizeJoin()
5387
.shouldBe(
5488
// language=TypeScript

docs/code/test/PolymorphismTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,34 @@ class PolymorphismTest {
206206
.normalize()
207207
)
208208
}
209+
210+
@Test
211+
fun testExampleGenerics01() {
212+
captureOutput("ExampleGenerics01") {
213+
dev.adamko.kxstsgen.example.exampleGenerics01.main()
214+
}.normalizeJoin()
215+
.shouldBe(
216+
// language=TypeScript
217+
"""
218+
|export interface Box {
219+
| value: number;
220+
|}
221+
""".trimMargin()
222+
.normalize()
223+
)
224+
}
225+
226+
@Test
227+
fun testExampleJsonPolymorphic01() {
228+
captureOutput("ExampleJsonPolymorphic01") {
229+
dev.adamko.kxstsgen.example.exampleJsonPolymorphic01.main()
230+
}.normalizeJoin()
231+
.shouldBe(
232+
// language=TypeScript
233+
"""
234+
|export type Project = any;
235+
""".trimMargin()
236+
.normalize()
237+
)
238+
}
209239
}

0 commit comments

Comments
 (0)