Giter Site home page Giter Site logo

adamko-dev / kotlinx-serialization-typescript-generator Goto Github PK

View Code? Open in Web Editor NEW
48.0 1.0 5.0 1.19 MB

KxsTsGen :: Generate TypeScript interfaces from Kotlin classes

Home Page: https://adamko-dev.github.io/kotlinx-serialization-typescript-generator/

License: Apache License 2.0

Kotlin 100.00%
kotlin kotlinx-serialization dukat kotlin-library kotlin-multiplatform kotlin-js kotlin-typescript typescript

kotlinx-serialization-typescript-generator's People

Contributors

asemy avatar renovate[bot] avatar tamj0rd2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

kotlinx-serialization-typescript-generator's Issues

Add an option to use 'brand type' on value classes and primitives

To help emulate Kotlin's strong typing, use 'brand typing' to specify primitives and value classes. Otherwise TypeScript doesn't discriminate between UInt and Long

Builtin primitives should be handled

type Byte = number & { __kotlin_byte__ : void};

Value classes too

package my.package

@Serializable
@JvmInline
value class UserCount(private val count: UInt)

Something like this...

type UInt = number & { __kotlin_uint__ : void};

type UserCount = UInt & { __my_package__usercount__ : void};

Handle generic properties and classes

This has a large scope. SerialDescriptors are not at all generic. It is probably impossible to get this information from KSX.

  1. Generic fields
    class WrapperClass(
      val type: List<String>
    )
  2. Generic classes
    class WrapperClass<T>(
      val type: T
    )
  3. Generic constraints
    interface MyInterface
    class WrapperClass<T: MyInterface>(
      val type: T
    )
  4. Inheritance plus generics
    interface MyInterface<T>
    class WrapperClass<T>(
      val type: T
    ) : MyInterface<T>
  5. Wildcards
    class SomeClass(
      val type: List<*>
    )  

Namespace for sealed class generated twice

Let's look on code

@Serializable
sealed class S {
    @Serializable
    class A : S()
    @Serializable
    class B: S()
}

@Serializable
class C(
    val s: S,
    val snull: S?
)

fun main() {
    val tsGenerator = KxsTsGenerator()
    println(tsGenerator.generate(serializer<C>()))
}

It generates the following type of script file, which is not valid.

export interface C {
  s: S;
  snull: S | null;
}

export type S =
  | S.A
  | S.B;

export namespace S {
  export enum Type {
    A = "S.A",
    B = "S.B",
  }
  
  export interface A {
    type: S.Type.A;
  }
  
  export interface B {
    type: S.Type.B;
  }
}

export namespace S {
  export enum Type {
    A = "S.A",
    B = "S.B",
  }
  
  export interface A {
    type: S.Type.A;
  }
  
  export interface B {
    type: S.Type.B;
  }
}

As I understood from debugger, there is some problems with handling nullable types, as there is something called S.A and something called S?.A, and deduplication doesn't work because of that.

Knit tests: check if the produced code is valid TypeScript

Currently the Knit tests only do a string comparison. But that doesn't mean the expected string is correct TypeScript...

  1. add the Gradle Node plugin, download TypeScript
  2. download TypeScript npx task
  3. in the tests, try compiling the generated TypeScript using a command line arg npx tsc (somehow? I'm not sure about this...)
  4. Check there's no errors (again, I'm not sure)

Support `@JsonDiscriminator`

kxs-ts-gen should detect @JsonDiscriminator, and use the @SerialName as a hardcoded value

@Serializable
@JsonDiscriminator("type")
sealed class SomeClass {
  val id: String
}

@SerialName("named")
@Serializable
data class NamedSomeClass(
  override val id: String,
  val name: String,
) : SomeClass()

Should convert to something like...

interface SomeClass<T extends "named"> {
  id: string;
  type: T
}

interface NamedSomeClass extends SomeClass {
  id: string;
  type: "named"
}

Generate enums closed-polymorphic classes, and use literal enum members as the discriminator

I want to be able to use literal enum members to discriminate classes

https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types

@Serializable
sealed class Project {
  abstract val name: String
}

@Serializable
class OwnedProject(override val name: String, val owner: String) : Project()

@Serializable
class DeprecatedProject(override val name: String, val reason: String) : Project()

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

should generate something like

enum ProjectKind {
  OwnedProject,
  DeprecatedProject,
}

interface Project {
  type: ProjectKind;
}

interface OwnedProject {
  type: ProjectKind.OwnedProject;
  name: string;
  owner: string;
}

interface DeprecatedProject {
  type: ProjectKind.DeprecatedProject;
  name: string;
  reason: string;
}

Export numeric types (Double, Float) as named type aliases, so generated TypeScript closely resembles Kotlin

Double and Int are both converted directly to number. This is confusing on the TypeScript side.

@Serializable
data class Position(
  val x: Double,
  val y: Double,
  val level: Int,
)
// generated
export interface Position {
  x: number;
  y: number;
  level: number; // level looks the same as x and y, but if I produce a JSON message with 1.5, KXS will error
}

Unsigned numbers (because they are value classes) are converted to type aliases, e.g.

export interface Score {
  goals: UInt;
}

export type UInt = number;

In TypeScript this isn't safe (UInt can accept any numeric value, despite its name) - but it gives a good indication, and can be enhanced with brand typing (#7).

Can the other numeric primitives be exported as type aliases?

export type Byte = number
export type Short = number
export type Int = number
export type Long = number
export type Float = number
export type Double = number

Handle naming conflicts from internally defined subclass

I have a use case here where kotlin have internal subclass

@Serializable
data class A(val c: C){
    enum class C{
        one
    }
}

@Serializable
data class B(val c: C){
    enum class C {
        two
    }
}

@Serializable
data class Test(
    val a: A,
    val b: B
)

the generated ts definition of class Test is

export interface Test {
  a: A;
  b: B;
}

export interface A {
  c: C;
}

export interface B {
  c: C;
}

export enum C {
  one = "one",
}

export enum C {
  two = "two",
}

which has some naming conflicts on enum C.
Do we have a way to handle this? I see some unimplemented namespace, could it be used here?

Provide a tuple serializer

Instead of

@Serializable
class Tiles(
  val tiles: List<Tile>,
)

@Serializable
data class Tile(
  val x: Int,
  val y: Int,
  val type: String,
)

being converted to

interface Tiles{
  tiles: Tile[];
}

interface Tile{
  x: Int;
  y: Int;
  type: string;
}

type Int = number

instead create a tuple, because it's more compact when it's encoded to JSON.

Something like...

@Serializable(with = TupleSerializer::class)
data class Tile(
  val x: Int,
  val y: Int,
  val type: String,
)
type Tile = [Int, Int, string]

This would also provide a good demonstration of how the generated Typescript is affected by custom descriptors.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Pending Status Checks

These updates await pending status checks. To force their creation now, click the checkbox below.

  • Update dependency @tsconfig/docusaurus to v2.0.3
  • Update dependency gradle to v8.7
  • Update dependency @mdx-js/react to v3
  • Update docusaurus monorepo to v3 (major) (@docusaurus/core, @docusaurus/module-type-aliases, @docusaurus/preset-classic)
  • Update react monorepo to v18 (major) (react, react-dom)

Pending Branch Automerge

These updates await pending status checks before automerging. Click on a checkbox to abort the branch automerge, and create a PR instead.

  • Update dependency typescript to v5.4.3

Detected dependencies

github-actions
.github/workflows/run_gradle_task.yml
  • actions/checkout v4
  • gradle/wrapper-validation-action v2
  • actions/setup-java v4
  • actions/cache v4
  • gradle/gradle-build-action v3
  • actions/upload-artifact v4
  • mikepenz/action-junit-report v4
.github/workflows/run_publish_site.yml
  • actions/checkout v4
  • gradle/wrapper-validation-action v2
  • actions/setup-java v4
  • actions/cache v4
  • gradle/gradle-build-action v3
  • actions/upload-pages-artifact v3
  • actions/deploy-pages v4
gradle
buildSrc/src/main/kotlin/buildsrc/config/KxsTsGenBuildSettings.kt
buildSrc/src/main/kotlin/buildsrc/config/distributions.kt
buildSrc/src/main/kotlin/buildsrc/config/gradle.kt
buildSrc/src/main/kotlin/buildsrc/config/kmm.kt
buildSrc/src/main/kotlin/buildsrc/config/nodePlugin.kt
gradle.properties
settings.gradle.kts
build.gradle.kts
buildSrc/settings.gradle.kts
buildSrc/build.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/base.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/knit-files.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/knit-tests.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/kotlin-mpp.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts
buildSrc/src/main/kotlin/buildsrc/convention/node.gradle.kts
docs/code/build.gradle.kts
gradle/libs.versions.toml
  • org.jetbrains.kotlin:kotlin-bom 1.9.23
  • com.google.devtools.ksp:symbol-processing-api 1.9.23-1.0.19
  • org.jetbrains.kotlinx:kotlinx-coroutines-bom 1.8.0
  • org.jetbrains.kotlinx:kotlinx-serialization-bom 1.6.3
  • com.squareup.okio:okio-bom 3.9.0
  • io.github.classgraph:classgraph 4.8.168
  • com.github.pgreze:kotlin-process 1.4.1
  • com.github.tschuchortdev:kotlin-compile-testing 1.5.0
  • com.github.tschuchortdev:kotlin-compile-testing-ksp 1.5.0
  • io.kotest:kotest-bom 5.8.1
  • org.jetbrains.kotlinx:kotlinx-knit 0.5.0
  • org.jetbrains.kotlinx:kotlinx-knit-test 0.5.0
  • org.jetbrains.kotlin:kotlin-gradle-plugin 1.9.23
  • org.jetbrains.kotlin:kotlin-serialization 1.9.23
  • com.github.node-gradle:gradle-node-plugin 7.0.2
  • org.jetbrains.kotlinx:kover 0.6.1
  • io.kotest:kotest-framework-multiplatform-plugin-gradle 5.8.1
  • com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin 1.9.23-1.0.19
modules/kxs-ts-gen-core/build.gradle.kts
modules/kxs-ts-gen-processor/build.gradle.kts
modules/versions-platform/build.gradle.kts
site/build.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.6
npm
site/package.json
  • @docusaurus/core 2.4.3
  • @docusaurus/preset-classic 2.4.3
  • @mdx-js/react ^1.6.22
  • clsx ^2.0.0
  • docusaurus-plugin-sass ^0.2.2
  • prism-react-renderer ^2.0.0
  • prism-themes ^1.9.0
  • prismjs-darcula-theme ^1.0.3
  • react ^17.0.2
  • react-dom ^17.0.2
  • typescript ^5.0.0
  • @docusaurus/module-type-aliases 2.4.3
  • @tsconfig/docusaurus ^2.0.0
  • node >=16.14

  • Check this box to trigger a request for Renovate to run again on this repository

Create a Gradle plugin, so TypeScript files are generated during compilation

The plugin will scan for all classes with @Serializable. It will convert each to a TypeScript definition.

Nice extras

  1. kxs-ts-gen is configurable

  2. Add a @TsExport annotation so only specific types are generated

  3. Allow @TsExport to be added to const strings, and the raw value will be exported (inspired by ntrrgc/ts-generator#24)

    @TsExport
    const val customConfig: String = """
    object ApiConstants {
        const SOME_VAL_1 = 1234
        const SOME_VAL_2 = "hello world"
    }
    """

    kxs-ts-gen will produce

    object ApiConstants {
        const SOME_VAL_1 = 1234
        const SOME_VAL_2 = "hello world"
    }

    Or maybe directly convert a const

    object ApiConstants {
      @TsExport
      const val SOME_VAL_1 = 1234
      @TsExport
      const val SOME_VAL_2 = "hello world"
    }

    kxs-ts-gen will produce

    object ApiConstants {
        const SOME_VAL_1 = 1234
        const SOME_VAL_2 = "hello world"
    }
  4. Grab KDoc, and convert it to JSDoc

  5. Grab annotations and convert them ntrrgc/ts-generator#23

  6. Add example for NPM upload (with mpetuska/npm-publish?) context

Use KSP to create a plugin. KSP is unsuitable, it can only view and can't run code, use https://github.com/ronmamo/reflections or https://github.com/classgraph/classgraph instead?

Publish on Maven

Hello, it would be great to have this package on maven.

In my case I'm stuck on Maven since am using cloud functions and they require maven pom.xml unless you want to build and upload a jar. All my dependencies including kotlinx.serialization are maven except this packages.

Provide an easier way to set default TypeScript elements, so generated code can be customised more easily

If a client already has some standard type aliases, then I want to re-use those type aliases in my generated code.

@Serializable
data class Position(
  val x: Double,
  val y: Double,
)
// provided by library
export type double = number

// I want generated code to use 'double'
export interface Position {
  x: double;
  y: double;
}

If I hack around a bit, I can come up with something that works. But it's not pretty, or intuitive

fun main() {
  val tsGenerator = KxsTsGenerator()

  val libraryDoubleTypeRef = TsTypeRef.Declaration(TsElementId("Double"), null, false)

  tsGenerator.descriptorElements += Double.serializer().descriptor to setOf(
    TsDeclaration.TsTypeAlias(
      TsElementId("Double"),
      TsTypeRef.Declaration(TsElementId("double"), null, false),
    )
  )

  tsGenerator.descriptorTypeRef += mapOf(
    Double.serializer().descriptor to libraryDoubleTypeRef
  )

  println(tsGenerator.generate(Position.serializer()))
}

@Serializable
data class Position(
  val x: Double,
  val y: Double,
)

This produces the correct output...

export interface Position {
  x: Double;
  y: Double;
}

export type Double = double;

Good luck figuring that out if you're new to KXS, and didn't happen to build this library...

Potential solutions

Users should be able to set overrides for SerialDescriptors, similar to the existing serializerDescriptors and descriptorElements in KxsTsGenerator. For example

val gen = KxsTsGenerator()

gen.addOverride(
  Double.serializer().descriptor to existingTypeAlias("double"),
)

Implement collections

Any sort of Kotlin collection, e.g. List<String> should be converted to a TypeScript array, string[]

Lists of any type should be supported.

  • Nested lists List<List<String>>
  • Lists of maps List<Map<String, String>>
  • Lists of objects List<SomeKtObject>
  • Lists of structures, like data classes, classes, or interfaces
  • Lists of enums

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.