Skip to content

Commit 3ae531f

Browse files
committed
- 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 8db48ce commit 3ae531f

File tree

7 files changed

+148
-49
lines changed

7 files changed

+148
-49
lines changed

README.md

+15-14
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,18 @@ See [the docs](./docs) for working examples.
5555

5656
This is a proof-of-concept.
5757

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-
| 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

+1-1
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

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+
}

docs/code/test/PolymorphismTest.kt

+30
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
}

docs/polymorphism.md

+51-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* [Objects](#objects)
1414
* [Open Polymorphism](#open-polymorphism)
1515
* [Generics](#generics)
16+
* [JSON content polymorphism](#json-content-polymorphism)
1617

1718
<!--- END -->
1819

@@ -346,13 +347,14 @@ export namespace Response {
346347

347348
### Generics
348349

349-
<!--- INCLUDE
350-
import kotlinx.serialization.builtins.serializer
351-
-->
350+
Kotlinx Serialization doesn't have 'generic' SerialDescriptors, so KxsTsGen can't generate generic
351+
TypeScript classes.
352352

353353
```kotlin
354+
import kotlinx.serialization.builtins.serializer
355+
354356
@Serializable
355-
class Box<T>(
357+
class Box<T : Number>(
356358
val value: T,
357359
)
358360

@@ -370,9 +372,51 @@ fun main() {
370372
> You can get the full code [here](./code/example/example-generics-01.kt).
371373
372374
```typescript
373-
export type Double = number & { __kotlin_Double__: void }
374-
375375
export interface Box {
376-
value: Double
376+
value: number;
377377
}
378378
```
379+
380+
<!--- TEST -->
381+
382+
### JSON content polymorphism
383+
384+
Using a
385+
[`JsonContentPolymorphicSerializer`](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html)
386+
means there's not enough data in the `SerialDescriptor` to generate a TypeScript interface. Instead,
387+
a named type alias to 'any' will be created instead.
388+
389+
```kotlin
390+
import kotlinx.serialization.json.*
391+
392+
@Serializable
393+
abstract class Project {
394+
abstract val name: String
395+
}
396+
397+
@Serializable
398+
data class BasicProject(override val name: String) : Project()
399+
400+
@Serializable
401+
data class OwnedProject(override val name: String, val owner: String) : Project()
402+
403+
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
404+
override fun selectDeserializer(element: JsonElement) = when {
405+
"owner" in element.jsonObject -> OwnedProject.serializer()
406+
else -> BasicProject.serializer()
407+
}
408+
}
409+
410+
fun main() {
411+
val tsGenerator = KxsTsGenerator()
412+
println(tsGenerator.generate(ProjectSerializer))
413+
}
414+
```
415+
416+
> You can get the full code [here](./code/example/example-json-polymorphic-01.kt).
417+
418+
```typescript
419+
export type Project = any;
420+
```
421+
422+
<!--- TEST -->

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ fun interface TsElementConverter {
6767
descriptor: SerialDescriptor,
6868
): TsDeclaration {
6969

70+
if (descriptor.elementsCount == 0) {
71+
return TsDeclaration.TsTypeAlias(
72+
elementIdConverter(descriptor),
73+
TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false)
74+
)
75+
}
76+
7077
val discriminatorIndex = descriptor.elementDescriptors
7178
.indexOfFirst { it.kind == PrimitiveKind.STRING }
7279
val discriminatorName = descriptor.getElementName(discriminatorIndex)
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package dev.adamko.kxstsgen
22

3-
import kotlinx.serialization.descriptors.PolymorphicKind
4-
import kotlinx.serialization.descriptors.PrimitiveKind
53
import kotlinx.serialization.descriptors.SerialDescriptor
6-
import kotlinx.serialization.descriptors.SerialKind
7-
import kotlinx.serialization.descriptors.StructureKind
84

95

106
fun interface TsElementIdConverter {
@@ -13,30 +9,20 @@ fun interface TsElementIdConverter {
139

1410
object Default : TsElementIdConverter {
1511
override operator fun invoke(descriptor: SerialDescriptor): TsElementId {
16-
val targetId = TsElementId(descriptor.serialName.removeSuffix("?"))
17-
18-
return when (descriptor.kind) {
19-
PolymorphicKind.OPEN -> TsElementId(
20-
targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">")
21-
)
22-
PolymorphicKind.SEALED,
23-
PrimitiveKind.BOOLEAN,
24-
PrimitiveKind.BYTE,
25-
PrimitiveKind.CHAR,
26-
PrimitiveKind.DOUBLE,
27-
PrimitiveKind.FLOAT,
28-
PrimitiveKind.INT,
29-
PrimitiveKind.LONG,
30-
PrimitiveKind.SHORT,
31-
PrimitiveKind.STRING,
32-
SerialKind.CONTEXTUAL,
33-
SerialKind.ENUM,
34-
StructureKind.CLASS,
35-
StructureKind.LIST,
36-
StructureKind.MAP,
37-
StructureKind.OBJECT -> targetId
12+
13+
val serialName = descriptor.serialName.removeSuffix("?")
14+
15+
val namespace = serialName.substringBeforeLast('.')
16+
17+
val id = serialName
18+
.substringAfterLast('.')
19+
.substringAfter("<")
20+
.substringBeforeLast(">")
21+
22+
return when {
23+
namespace.isBlank() -> TsElementId("$id")
24+
else -> TsElementId("$namespace.$id")
3825
}
3926
}
40-
4127
}
4228
}

0 commit comments

Comments
 (0)