diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsAnnotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsAnnotations.kt new file mode 100644 index 00000000..28621dd2 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsAnnotations.kt @@ -0,0 +1,18 @@ +package dev.adamko.kxstsgen + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.InheritableSerialInfo + +/** + * Marks a property as required in the generated TypeScript interface. + * + * This annotation is inheritable, so it should be sufficient to place it on a base class of hierarchy. + * + * This annotation should only be used if [kotlinx.serialization.json.JsonConfiguration.encodeDefaults] + * is set to true. If it is false (which it is by default), then properties with default values are + * potentially omitted from the generated JSON. + */ +@InheritableSerialInfo +@Target(AnnotationTarget.PROPERTY) +@ExperimentalSerializationApi +annotation class KxsTsRequired diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt index 9d9c4b7b..2da10dc9 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen.core +import dev.adamko.kxstsgen.KxsTsRequired import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -207,7 +208,9 @@ fun interface TsElementConverter { return descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> val name = descriptor.getElementName(index) val fieldTypeRef = typeRefConverter(fieldDescriptor) - val optional = descriptor.isElementOptional(index) + val optional = + descriptor.getElementAnnotations(index).none { it is KxsTsRequired } + && descriptor.isElementOptional(index) TsProperty(name, fieldTypeRef, optional) }.toSet() } diff --git a/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/KxsTsGeneratorTest.kt b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/KxsTsGeneratorTest.kt new file mode 100644 index 00000000..f6b7bf91 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/KxsTsGeneratorTest.kt @@ -0,0 +1,45 @@ +package dev.adamko.kxstsgen.core + +import dev.adamko.kxstsgen.KxsTsConfig +import dev.adamko.kxstsgen.KxsTsGenerator +import dev.adamko.kxstsgen.KxsTsRequired +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import kotlinx.serialization.Serializable + +class KxsTsGeneratorTest : FunSpec({ + val config = KxsTsConfig(indent = " ") + val tsGenerator = KxsTsGenerator(config) + + test("Can make default-valued properties required") { + @Serializable + data class OptionalTest( + val optional: Boolean = false, + @KxsTsRequired + val required: Boolean = true, + ) + + tsGenerator.generate(OptionalTest.serializer()) shouldBe """ + |export interface OptionalTest { + | optional?: boolean; + | required: boolean; + |} + """.trimMargin() + } + + test("Can make nullable properties required") { + @Serializable + data class OptionalTest( + @KxsTsRequired + val required: Boolean? = null, + val optional: Boolean? = null, + ) + + tsGenerator.generate(OptionalTest.serializer()) shouldBe """ + |export interface OptionalTest { + | required: boolean | null; + | optional?: boolean | null; + |} + """.trimMargin() + } +})