diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt index 27268c31..bd1b5d16 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt @@ -27,6 +27,7 @@ data class ClassSettings( object PropertyUtils { fun PropertyInfo.addToClass( + modelName: String, type: TypeName, parameterizedType: TypeName, classBuilder: TypeSpec.Builder, @@ -80,16 +81,17 @@ object PropertyUtils { ClassSettings.PolymorphyType.SUB -> { if (this is PropertyInfo.Field && isPolymorphicDiscriminator) { property.addModifiers(KModifier.OVERRIDE) - when (maybeDiscriminator) { - is PropertyInfo.DiscriminatorKey.EnumKey -> - property.initializer("%T.%L", wrappedType, maybeDiscriminator.enumKey) - - is PropertyInfo.DiscriminatorKey.StringKey -> - property.initializer("%S", maybeDiscriminator.stringValue) - - else -> { - property.addAnnotation(JacksonMetadata.jacksonParameterAnnotation(oasKey)) + val discriminators = maybeDiscriminator.getDiscriminatorMappings(modelName) + if (discriminators.size == 1) { + when (val discriminator = discriminators.first()) { + is PropertyInfo.DiscriminatorKey.EnumKey -> + property.initializer("%T.%L", wrappedType, discriminator.enumKey) + + is PropertyInfo.DiscriminatorKey.StringKey -> + property.initializer("%S", discriminator.stringValue) } + } else { + property.addAnnotation(JacksonMetadata.jacksonParameterAnnotation(oasKey)) } } else { if (isInherited) { @@ -111,7 +113,8 @@ object PropertyUtils { if (this !is PropertyInfo.Field || !isPolymorphicDiscriminator || - isSubTypeDiscriminatorWithNoValue(classSettings) + isSubTypeDiscriminatorWithNoValue(classSettings) || + isSubTypeDiscriminatorWithMultipleValues(classSettings, modelName) ) { property.initializer(name) val constructorParameter: ParameterSpec.Builder = ParameterSpec.builder(name, wrappedType) @@ -134,8 +137,20 @@ object PropertyUtils { classBuilder.addProperty(property.build()) } + private fun Map?.getDiscriminatorMappings( + modelName: String + ): List = + this?.filter { it.value.modelName == modelName }?.map {it.value}.orEmpty() + private fun PropertyInfo.Field.isSubTypeDiscriminatorWithNoValue(classType: ClassSettings) = - classType.polymorphyType == ClassSettings.PolymorphyType.SUB && isPolymorphicDiscriminator && maybeDiscriminator == null + classType.polymorphyType == ClassSettings.PolymorphyType.SUB && + isPolymorphicDiscriminator && + maybeDiscriminator == null + + private fun PropertyInfo.Field.isSubTypeDiscriminatorWithMultipleValues(classType: ClassSettings, modelName: String) = + classType.polymorphyType == ClassSettings.PolymorphyType.SUB && + isPolymorphicDiscriminator && + maybeDiscriminator.getDiscriminatorMappings(modelName).size > 1 private fun getDefaultValue(propTypeInfo: PropertyInfo, parameterizedType: TypeName): OasDefault? { return when (propTypeInfo) { diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt index 9bea448f..5f1e71fa 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt @@ -26,6 +26,7 @@ import com.cjbooms.fabrikt.model.PropertyInfo.Companion.HTTP_SETTINGS import com.cjbooms.fabrikt.model.PropertyInfo.Companion.topLevelProperties import com.cjbooms.fabrikt.model.SchemaInfo import com.cjbooms.fabrikt.model.SourceApi +import com.cjbooms.fabrikt.util.KaizenParserExtensions.getDiscriminatorForInLinedObjectUnderAllOf import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSuperType import com.cjbooms.fabrikt.util.KaizenParserExtensions.isComplexTypedAdditionalProperties import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedEnumDefinition @@ -168,6 +169,15 @@ class JacksonModelGenerator( ): TypeSpec { val modelName = schemaInfo.name.toModelClassName() return when { + schemaInfo.schema.isPolymorphicSuperType() && schemaInfo.schema.isPolymorphicSubType(api) -> + polymorphicSuperSubType( + modelName, + properties, + checkNotNull(schemaInfo.schema.getDiscriminatorForInLinedObjectUnderAllOf()), + schemaInfo.schema.getSuperType(api)!!.let { SchemaInfo(it.name, it) }, + schemaInfo.schema.extensions, + allSchemas + ) schemaInfo.schema.isPolymorphicSuperType() -> polymorphicSuperType( modelName, properties, @@ -342,58 +352,97 @@ class JacksonModelGenerator( .addMicronautReflectionAnnotation() .addCompanionObject() properties.addToClass( - classBuilder, - ClassSettings(ClassSettings.PolymorphyType.NONE, extensions.hasJsonMergePatchExtension) + modelName = modelName, + classBuilder = classBuilder, + classType = ClassSettings(ClassSettings.PolymorphyType.NONE, extensions.hasJsonMergePatchExtension) ) return classBuilder.build() } + private fun polymorphicSuperSubType( + modelName: String, + properties: Collection, + discriminator: Discriminator, + superType: SchemaInfo, + extensions: Map, + allSchemas: List + ): TypeSpec = with(FunSpec.constructorBuilder()) { + TypeSpec.classBuilder(generatedType(packages.base, modelName)) + .buildPolymorphicSubType(modelName, properties.filter(PropertyInfo::isInherited), superType, extensions, this) + .buildPolymorphicSuperType(modelName, properties.filterNot(PropertyInfo::isInherited), discriminator, extensions, allSchemas, this) + .build() + } + private fun polymorphicSuperType( + modelName: String, + properties: Collection, + discriminator: Discriminator, + extensions: Map, + allSchemas: List + ): TypeSpec = TypeSpec.classBuilder(generatedType(packages.base, modelName)) + .buildPolymorphicSuperType(modelName, properties, discriminator, extensions, allSchemas) + .build() + + private fun TypeSpec.Builder.buildPolymorphicSuperType( modelName: String, properties: Collection, discriminator: Discriminator, extensions: Map, allSchemas: List, - ): TypeSpec { - val classBuilder = TypeSpec.classBuilder(generatedType(packages.base, modelName)) - .addModifiers(KModifier.SEALED) + constructorBuilder: FunSpec.Builder = FunSpec.constructorBuilder() + ): TypeSpec.Builder { + this.addModifiers(KModifier.SEALED) .addAnnotation(basePolymorphicType(discriminator.propertyName)) + .modifiers.remove(KModifier.DATA) val subTypes = allSchemas .filter { model -> model.schema.allOfSchemas.any { allOfRef -> - allOfRef.name?.toModelClassName() == modelName && allOfRef.discriminator == discriminator + allOfRef.name?.toModelClassName() == modelName && + (allOfRef.discriminator == discriminator || + allOfRef.allOfSchemas.any { it.discriminator == discriminator }) } } - val mappings = subTypes.flatMap { schemaInfo -> - discriminator.mappingKeys(schemaInfo.schema).map { - it to toModelType(packages.base, KotlinTypeInfo.from(schemaInfo.schema, schemaInfo.name)) - } - }.toMap() + + val mappings: Map = subTypes.map { schemaInfo -> + discriminator.getDiscriminatorMappings(schemaInfo) + }.toMapList() + .firstValueMap() + val maybeEnumDiscriminator = properties .firstOrNull { it.name == discriminator.propertyName }?.typeInfo as? KotlinTypeInfo.Enum - classBuilder.addAnnotation(polymorphicSubTypes(mappings, maybeEnumDiscriminator)) + this.addAnnotation(polymorphicSubTypes(mappings, maybeEnumDiscriminator)) .addQuarkusReflectionAnnotation() .addMicronautIntrospectedAnnotation() .addMicronautReflectionAnnotation() properties.addToClass( - classBuilder, + modelName, + constructorBuilder, + this, ClassSettings(ClassSettings.PolymorphyType.SUPER, extensions.hasJsonMergePatchExtension) ) - return classBuilder.build() + return this } private fun polymorphicSubType( modelName: String, properties: Collection, superType: SchemaInfo, + extensions: Map + ): TypeSpec = TypeSpec.classBuilder(generatedType(packages.base, modelName)) + .buildPolymorphicSubType(modelName, properties, superType, extensions).build() + + private fun TypeSpec.Builder.buildPolymorphicSubType( + modelName: String, + allProperties: Collection, + superType: SchemaInfo, extensions: Map, - ): TypeSpec { - val classBuilder = TypeSpec.classBuilder(generatedType(packages.base, modelName)) - .addSerializableInterface() + constructorBuilder: FunSpec.Builder = FunSpec.constructorBuilder() + ): TypeSpec.Builder { + this.addSerializableInterface() .addQuarkusReflectionAnnotation() .addMicronautIntrospectedAnnotation() .addMicronautReflectionAnnotation() @@ -401,20 +450,34 @@ class JacksonModelGenerator( .superclass( toModelType(packages.base, KotlinTypeInfo.from(superType.schema, superType.name)) ) + + val properties = superType.schema.getDiscriminatorForInLinedObjectUnderAllOf()?.let { discriminator -> + allProperties.filterNot { + when (it) { + is PropertyInfo.Field -> it.isPolymorphicDiscriminator && it.name != discriminator.propertyName + else -> false + } + } + } ?: allProperties + properties.addToClass( - classBuilder, + modelName, + constructorBuilder, + this, ClassSettings(ClassSettings.PolymorphyType.SUB, extensions.hasJsonMergePatchExtension) ) - return classBuilder.build() + return this } private fun Collection.addToClass( + modelName: String, + constructorBuilder: FunSpec.Builder = FunSpec.constructorBuilder(), classBuilder: TypeSpec.Builder, - classType: ClassSettings + classType: ClassSettings, ): TypeSpec.Builder { - val constructorBuilder = FunSpec.constructorBuilder() this.forEach { it.addToClass( + modelName, toModelType( packages.base, it.typeInfo, @@ -435,6 +498,23 @@ class JacksonModelGenerator( return classBuilder.primaryConstructor(constructorBuilder.build()) } + private fun Discriminator.getDiscriminatorMappings(schemaInfo: SchemaInfo): Map = + mappingKeys(schemaInfo.schema) + .filter { it.value == schemaInfo.schema.name } + .map { + it.key to toModelType(packages.base, KotlinTypeInfo.from(schemaInfo.schema, schemaInfo.name)) + } + .toMap() + + private fun List>.toMapList(): Map> = + asSequence() + .flatMap { + it.asSequence() + }.groupBy({ it.key }, { it.value }) + + private fun Map>.firstValueMap(): Map = + map { it.key to it.value.first() }.toMap() + private fun TypeSpec.Builder.addSerializableInterface(): TypeSpec.Builder { if (options.any { it == ModelCodeGenOptionType.JAVA_SERIALIZATION }) this.addSuperinterface(Serializable::class) diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt index d9c6b83f..93547108 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt @@ -11,6 +11,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedObjectDefinition import com.cjbooms.fabrikt.util.KaizenParserExtensions.isRequired import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSchemaLess import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleMapDefinition +import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeType import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName import com.cjbooms.fabrikt.util.NormalisedString.camelCase @@ -44,6 +45,7 @@ sealed class PropertyInfo { it.topLevelProperties( maybeMarkInherited( settings, + enclosingSchema, it ), this @@ -55,8 +57,15 @@ sealed class PropertyInfo { return results.distinctBy { it.oasKey } } - private fun maybeMarkInherited(settings: Settings, it: Schema) = - settings.copy(markAsInherited = if (it.isInLinedObjectUnderAllOf() || it.hasNoDiscriminator()) settings.markAsInherited else true) + private fun maybeMarkInherited(settings: Settings, enclosingSchema: Schema?, it: Schema): Settings { + val isInherited = when { + it.safeName() == enclosingSchema?.name -> false + it.hasNoDiscriminator() -> settings.markAsInherited + it.isInLinedObjectUnderAllOf() && it.hasNoDiscriminator() -> settings.markAsInherited + else -> true + } + return settings.copy(markAsInherited = isInherited) + } private fun Schema.getInLinedProperties( settings: Settings, @@ -135,9 +144,9 @@ sealed class PropertyInfo { } } - sealed class DiscriminatorKey(val stringValue: String) { - class StringKey(value: String) : DiscriminatorKey(value) - class EnumKey(value: String) : DiscriminatorKey(value) { + sealed class DiscriminatorKey(val stringValue: String, val modelName: String) { + class StringKey(value: String, modelName: String) : DiscriminatorKey(value, modelName) + class EnumKey(value: String, modelName: String) : DiscriminatorKey(value, modelName) { val enumKey = value.toEnumName() } } @@ -148,7 +157,7 @@ sealed class PropertyInfo { override val schema: Schema, override val isInherited: Boolean, val isPolymorphicDiscriminator: Boolean, - val maybeDiscriminator: DiscriminatorKey?, + val maybeDiscriminator: Map?, val enclosingSchema: Schema? = null ) : PropertyInfo() { override val typeInfo: KotlinTypeInfo = diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt index 5b6199c3..32f50bd9 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt @@ -36,7 +36,8 @@ object KaizenParserExtensions { private const val EXTENSIBLE_ENUM_KEY = "x-extensible-enum" - fun Schema.isPolymorphicSuperType(): Boolean = discriminator?.propertyName != null + fun Schema.isPolymorphicSuperType(): Boolean = discriminator?.propertyName != null || + getDiscriminatorForInLinedObjectUnderAllOf()?.propertyName != null fun Schema.isInlinedObjectDefinition() = isObjectType() && !isSchemaLess() && Overlay.of(this).pathFromRoot.contains("properties") @@ -113,10 +114,17 @@ object KaizenParserExtensions { private fun Schema.getSchemaNameInParent(): String? = Overlay.of(this).pathInParent fun Schema.isPolymorphicSubType(api: OpenApi3): Boolean = - getEnclosingSchema(api)?.let { it.allOfSchemas.any { it.discriminator.propertyName != null } } ?: false + getEnclosingSchema(api)?.let { schema -> + schema.allOfSchemas.any { it.isPolymorphicSuperType() } + } ?: false fun Schema.getSuperType(api: OpenApi3): Schema? = - getEnclosingSchema(api)?.let { it.allOfSchemas.firstOrNull { it.discriminator.propertyName != null } } + getEnclosingSchema(api)?.let { schema -> + schema.allOfSchemas.firstOrNull { it.isPolymorphicSuperType() } + } + + fun Schema.getDiscriminatorForInLinedObjectUnderAllOf(): Discriminator? = + this.allOfSchemas.firstOrNull { it.isInLinedObjectUnderAllOf() }?.discriminator private fun Schema.getEnclosingSchema(api: OpenApi3): Schema? = api.schemas.values.firstOrNull { it.name == safeName() } @@ -136,19 +144,19 @@ object KaizenParserExtensions { fun Schema.getKeyIfSingleDiscriminatorValue( prop: Map.Entry, enclosingSchema: Schema - ): PropertyInfo.DiscriminatorKey? = - if (isDiscriminatorProperty(prop) && discriminator.mappingKeys(enclosingSchema).size == 1) { - discriminator.mappingKeys(enclosingSchema).first().let { - if (prop.value.isEnumDefinition()) PropertyInfo.DiscriminatorKey.EnumKey(it) - else PropertyInfo.DiscriminatorKey.StringKey(it) - } + ): Map? = + if (isDiscriminatorProperty(prop) && discriminator.mappingKeys(enclosingSchema).isNotEmpty()) { + discriminator.mappingKeys(enclosingSchema).map { + if (prop.value.isEnumDefinition()) it.key to PropertyInfo.DiscriminatorKey.EnumKey(it.key, it.value) + else it.key to PropertyInfo.DiscriminatorKey.StringKey(it.key, it.value) + }.toMap() } else null - fun Discriminator.mappingKeys(enclosingSchema: Schema): List { - val keys = mappings?.entries?.filter { - it.value.toString().contains(enclosingSchema.name) - }?.map { it.key } - return if (keys.isNullOrEmpty()) listOf(enclosingSchema.safeName().toModelClassName()) else keys + fun Discriminator.mappingKeys(enclosingSchema: Schema): Map { + val discriminatorMappings = mappings?.map { it.key to it.value.split("/").last() }?.toMap() + return if (discriminatorMappings.isNullOrEmpty()) { + mapOf(enclosingSchema.name to enclosingSchema.safeName().toModelClassName()) + } else discriminatorMappings } fun Schema.isInLinedObjectUnderAllOf(): Boolean = diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt index 7b14a21d..2b35b231 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt @@ -43,6 +43,7 @@ class ModelGeneratorTest { "oneOfPolymorphicModels", "optionalVsRequired", "polymorphicModels", + "nestedPolymorphicModels", "requiredReadOnly", "validationAnnotations", "wildCardTypes", diff --git a/src/test/resources/examples/nestedPolymorphicModels/api.yaml b/src/test/resources/examples/nestedPolymorphicModels/api.yaml new file mode 100644 index 00000000..cc3b623c --- /dev/null +++ b/src/test/resources/examples/nestedPolymorphicModels/api.yaml @@ -0,0 +1,157 @@ +openapi: 3.0.0 +paths: {} +info: + title: "" + version: "" +components: + schemas: + RootDiscriminator: + type: string + enum: + - firstLevelChild + + FirstLevelDiscriminator: + type: string + enum: + - secondLevelChild1 + - secondLevelChild2 + + SecondLevelDiscriminator: + type: string + enum: + - thirdLevelChild1 + - thirdLevelChild2 + + RootType: + type: object + required: + - rootField1 + discriminator: + propertyName: rootDiscriminator + mapping: + firstLevelChild: '#/components/schemas/FirstLevelChild' + properties: + rootDiscriminator: + $ref: "#/components/schemas/RootDiscriminator" + rootField1: + type: string + rootField2: + type: boolean + + FirstLevelChild: + allOf: + - $ref: '#/components/schemas/RootType' + - type: object + required: + - firstLevelField1 + discriminator: + propertyName: firstLevelDiscriminator + mapping: + secondLevelChild1: '#/components/schemas/SecondLevelChild1' + secondLevelChild2: '#/components/schemas/SecondLevelChild2' + properties: + firstLevelDiscriminator: + $ref: '#/components/schemas/FirstLevelDiscriminator' + firstLevelField1: + type: string + firstLevelField2: + type: integer + + SecondLevelChild1: + allOf: + - $ref: '#/components/schemas/FirstLevelChild' + - type: object + required: + - metadata + discriminator: + propertyName: secondLevelDiscriminator + mapping: + thirdLevelChild1: '#/components/schemas/ThirdLevelChild11' + thirdLevelChild2: '#/components/schemas/ThirdLevelChild12' + properties: + secondLevelDiscriminator: + $ref: '#/components/schemas/SecondLevelDiscriminator' + metadata: + $ref: '#/components/schemas/SecondLevelMetadata' + + SecondLevelChild2: + allOf: + - $ref: '#/components/schemas/FirstLevelChild' + - type: object + required: + - metadata + discriminator: + propertyName: secondLevelDiscriminator + mapping: + thirdLevelChild1: '#/components/schemas/ThirdLevelChild21' + thirdLevelChild2: '#/components/schemas/ThirdLevelChild22' + properties: + secondLevelDiscriminator: + $ref: '#/components/schemas/SecondLevelDiscriminator' + metadata: + $ref: '#/components/schemas/SecondLevelMetadata' + + SecondLevelMetadata: + type: object + required: + - obj + properties: + obj: + $ref: "#/components/schemas/CommonObject" + + ThirdLevelChild11: + allOf: + - $ref: '#/components/schemas/SecondLevelChild1' + - type: object + required: + - creationDate + properties: + creationDate: + type: integer + description: timestamp + + ThirdLevelChild12: + allOf: + - $ref: '#/components/schemas/SecondLevelChild1' + - type: object + required: + - isDeleted + properties: + isDeleted: + type: boolean + + ThirdLevelChild21: + allOf: + - $ref: '#/components/schemas/SecondLevelChild2' + - type: object + required: + - creationDate + properties: + creationDate: + type: integer + description: timestamp + + ThirdLevelChild22: + allOf: + - $ref: '#/components/schemas/SecondLevelChild2' + - type: object + required: + - isDeleted + properties: + isDeleted: + type: boolean + + CommonObject: + type: object + required: + - filed1 + - field2 + properties: + filed1: + type: string + required: true + nullable: false + field2: + type: string + required: true + nullable: false \ No newline at end of file diff --git a/src/test/resources/examples/nestedPolymorphicModels/models/Models.kt b/src/test/resources/examples/nestedPolymorphicModels/models/Models.kt new file mode 100644 index 00000000..4329349d --- /dev/null +++ b/src/test/resources/examples/nestedPolymorphicModels/models/Models.kt @@ -0,0 +1,337 @@ +package examples.nestedPolymorphicModels.models + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonValue +import javax.validation.Valid +import javax.validation.constraints.NotNull +import kotlin.Boolean +import kotlin.Int +import kotlin.String +import kotlin.collections.Map + +data class CommonObject( + @param:JsonProperty("filed1") + @get:JsonProperty("filed1") + @get:NotNull + val filed1: String, + @param:JsonProperty("field2") + @get:JsonProperty("field2") + @get:NotNull + val field2: String +) + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "firstLevelDiscriminator", + visible = true +) +@JsonSubTypes( + JsonSubTypes.Type( + value = SecondLevelChild1::class, + name = + "secondLevelChild1" + ), + JsonSubTypes.Type( + value = SecondLevelChild2::class, + name = + "secondLevelChild2" + ) +) +sealed class FirstLevelChild( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + open val firstLevelField1: String, + open val firstLevelField2: Int? = null +) : RootType(rootField1, rootField2) { + @get:JsonProperty("rootDiscriminator") + @get:NotNull + override val rootDiscriminator: RootDiscriminator = RootDiscriminator.FIRST_LEVEL_CHILD + + abstract val firstLevelDiscriminator: FirstLevelDiscriminator +} + +enum class FirstLevelDiscriminator( + @JsonValue + val value: String +) { + SECOND_LEVEL_CHILD1("secondLevelChild1"), + + SECOND_LEVEL_CHILD2("secondLevelChild2"); + + companion object { + private val mapping: Map = + values().associateBy(FirstLevelDiscriminator::value) + + fun fromValue(value: String): FirstLevelDiscriminator? = mapping[value] + } +} + +enum class RootDiscriminator( + @JsonValue + val value: String +) { + FIRST_LEVEL_CHILD("firstLevelChild"); + + companion object { + private val mapping: Map = + values().associateBy(RootDiscriminator::value) + + fun fromValue(value: String): RootDiscriminator? = mapping[value] + } +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "rootDiscriminator", + visible = true +) +@JsonSubTypes(JsonSubTypes.Type(value = FirstLevelChild::class, name = "firstLevelChild")) +sealed class RootType( + open val rootField1: String, + open val rootField2: Boolean? = null +) { + abstract val rootDiscriminator: RootDiscriminator +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "secondLevelDiscriminator", + visible = true +) +@JsonSubTypes( + JsonSubTypes.Type( + value = ThirdLevelChild11::class, + name = + "thirdLevelChild1" + ), + JsonSubTypes.Type( + value = ThirdLevelChild12::class, + name = + "thirdLevelChild2" + ) +) +sealed class SecondLevelChild1( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + open val metadata: SecondLevelMetadata +) : FirstLevelChild(rootField1, rootField2, firstLevelField1, firstLevelField2) { + @get:JsonProperty("firstLevelDiscriminator") + @get:NotNull + override val firstLevelDiscriminator: FirstLevelDiscriminator = + FirstLevelDiscriminator.SECOND_LEVEL_CHILD1 + + abstract val secondLevelDiscriminator: SecondLevelDiscriminator +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "secondLevelDiscriminator", + visible = true +) +@JsonSubTypes( + JsonSubTypes.Type( + value = ThirdLevelChild21::class, + name = + "thirdLevelChild1" + ), + JsonSubTypes.Type( + value = ThirdLevelChild22::class, + name = + "thirdLevelChild2" + ) +) +sealed class SecondLevelChild2( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + open val metadata: SecondLevelMetadata +) : FirstLevelChild(rootField1, rootField2, firstLevelField1, firstLevelField2) { + @get:JsonProperty("firstLevelDiscriminator") + @get:NotNull + override val firstLevelDiscriminator: FirstLevelDiscriminator = + FirstLevelDiscriminator.SECOND_LEVEL_CHILD2 + + abstract val secondLevelDiscriminator: SecondLevelDiscriminator +} + +enum class SecondLevelDiscriminator( + @JsonValue + val value: String +) { + THIRD_LEVEL_CHILD1("thirdLevelChild1"), + + THIRD_LEVEL_CHILD2("thirdLevelChild2"); + + companion object { + private val mapping: Map = + values().associateBy(SecondLevelDiscriminator::value) + + fun fromValue(value: String): SecondLevelDiscriminator? = mapping[value] + } +} + +data class SecondLevelMetadata( + @param:JsonProperty("obj") + @get:JsonProperty("obj") + @get:NotNull + @get:Valid + val obj: CommonObject +) + +data class ThirdLevelChild11( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + @param:JsonProperty("metadata") + @get:JsonProperty("metadata") + @get:NotNull + @get:Valid + override val metadata: SecondLevelMetadata, + @param:JsonProperty("creationDate") + @get:JsonProperty("creationDate") + @get:NotNull + val creationDate: Int +) : SecondLevelChild1(rootField1, rootField2, firstLevelField1, firstLevelField2, metadata) { + @get:JsonProperty("secondLevelDiscriminator") + @get:NotNull + override val secondLevelDiscriminator: SecondLevelDiscriminator = + SecondLevelDiscriminator.THIRD_LEVEL_CHILD1 +} + +data class ThirdLevelChild12( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + @param:JsonProperty("metadata") + @get:JsonProperty("metadata") + @get:NotNull + @get:Valid + override val metadata: SecondLevelMetadata, + @param:JsonProperty("isDeleted") + @get:JsonProperty("isDeleted") + @get:NotNull + val isDeleted: Boolean +) : SecondLevelChild1(rootField1, rootField2, firstLevelField1, firstLevelField2, metadata) { + @get:JsonProperty("secondLevelDiscriminator") + @get:NotNull + override val secondLevelDiscriminator: SecondLevelDiscriminator = + SecondLevelDiscriminator.THIRD_LEVEL_CHILD2 +} + +data class ThirdLevelChild21( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + @param:JsonProperty("metadata") + @get:JsonProperty("metadata") + @get:NotNull + @get:Valid + override val metadata: SecondLevelMetadata, + @param:JsonProperty("creationDate") + @get:JsonProperty("creationDate") + @get:NotNull + val creationDate: Int +) : SecondLevelChild2(rootField1, rootField2, firstLevelField1, firstLevelField2, metadata) { + @get:JsonProperty("secondLevelDiscriminator") + @get:NotNull + override val secondLevelDiscriminator: SecondLevelDiscriminator = + SecondLevelDiscriminator.THIRD_LEVEL_CHILD1 +} + +data class ThirdLevelChild22( + @param:JsonProperty("rootField1") + @get:JsonProperty("rootField1") + @get:NotNull + override val rootField1: String, + @param:JsonProperty("rootField2") + @get:JsonProperty("rootField2") + override val rootField2: Boolean? = null, + @param:JsonProperty("firstLevelField1") + @get:JsonProperty("firstLevelField1") + @get:NotNull + override val firstLevelField1: String, + @param:JsonProperty("firstLevelField2") + @get:JsonProperty("firstLevelField2") + override val firstLevelField2: Int? = null, + @param:JsonProperty("metadata") + @get:JsonProperty("metadata") + @get:NotNull + @get:Valid + override val metadata: SecondLevelMetadata, + @param:JsonProperty("isDeleted") + @get:JsonProperty("isDeleted") + @get:NotNull + val isDeleted: Boolean +) : SecondLevelChild2(rootField1, rootField2, firstLevelField1, firstLevelField2, metadata) { + @get:JsonProperty("secondLevelDiscriminator") + @get:NotNull + override val secondLevelDiscriminator: SecondLevelDiscriminator = + SecondLevelDiscriminator.THIRD_LEVEL_CHILD2 +}