vojtechhabarta / typescript-generator

Generates TypeScript from Java - JSON declarations, REST service client
MIT License
1.14k stars 237 forks source link

Class ENUMs incorrectly output as union type when using mapEnum asEnum #1055

Open garbit opened 5 months ago

garbit commented 5 months ago

Hi there,

I have a kotlin enum in my data class file that seems to output a string union type in my TS implementation file as well as an enum definition. I was expecting my interface to use the enum rather than the string union type. How can I configure the typescript generation step to achieve this?

Enum:

import com.fasterxml.jackson.annotation.JsonValue

enum class ContentItemType(
    @JsonValue
    val id: Int,
    val pathPrefix: String
) {
    THING(id = 0, pathPrefix = "content_thing"),

    companion object {
        fun fromId(id: Int): ContentItemType {
            return entries.find { it.id == id } ?: THING
        }
    }
}

Class:

import java.util.*

data class ThingContentItem(
    override val type: ContentItemType = ContentItemType.THING,
    val title: String,
): DataContentItem

DataContentItem

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "type"
)
@JsonSubTypes(
    value = [
        JsonSubTypes.Type(value = ThingContentItemDto::class, name = "0"),
    ]
)
sealed interface DataContentItem {
    val type: ContentItemType
}

I use the following settings in my gradle file:

tasks {
    generateTypeScript {
        jsonLibrary = JsonLibrary.jackson2
        outputKind = TypeScriptOutputKind.module
        outputFileType = TypeScriptFileType.implementationFile
        classPatterns = listOf("my.package.here.**")
        excludeClassPatterns = listOf("**Companion")
        outputFile = "[outputlocation]"
        mapEnum = EnumMapping.asEnum
        sortTypeDeclarations = true
    }
}

My Typescript output looks like this:

export const enum ContentItemType {
    THING = 0
}

export interface ThingDataContentDto extends DataContentItem {
    type: '0'; # <- I was expecting this to be an ENUM and not a union
    title: string;
}

I was expecting the ThingDataContentDto would use the ContentItemType.THING enum.

How can I achieve this / what am I doing wrong with the configuration?

garbit commented 5 months ago

Any updates on this?

My only solution so far is to perform a regex on the resulting ts file:

doLast {
    val file = File(project.projectDir, "kotlin-dtos.ts")
    if (!file.exists()) {
        throw RuntimeException("File not found: ${file.absolutePath}")
    }

    var content = file.readText()

    // Enum mapping from TypeScript number to enum
    val mapping = mapOf(
        "0" to "ContentItemType.THING",
    )

    // Regex patterns to find and replace types
    val patterns = listOf(
        Regex("""(?<=interface DataContentItem \{\s+type: )("[0-9]+"(?:\s*\|\s*"[0-9]+")*;)"""),
    )

    patterns.forEach { regex ->
        content = content.replace(regex) { matchResult ->
            matchResult.value
                .removeSurrounding("\"", ";")
                .split(" | ")
                .joinToString(" | ") { num -> mapping[num.trim('"')] ?: "ContentItemType.UNKNOWN" } + ";"
        }
    }

    file.writeText(content)
    println("Updated TypeScript definitions in ${file.absolutePath}")
}