From f8b815a37d2b3572052828f8ac9690408d25bc9a Mon Sep 17 00:00:00 2001 From: altro3 Date: Wed, 4 Dec 2024 15:41:29 +0700 Subject: [PATCH] Added new integration test for #875 --- .../example-kotlin-ksp/build.gradle.kts | 1 + .../src/main/kotlin/example/Application.kt | 4 +- .../openapi/test/api/RequestBodyApi.kt | 21 ++ .../openapi/test/api/RequestBodyApiImpl.kt | 11 + .../example/openapi/test/model/Animal.kt | 64 ++++++ .../kotlin/example/openapi/test/model/Bird.kt | 76 +++++++ .../example/openapi/test/model/ColorEnum.kt | 58 +++++ .../example/openapi/test/model/Error.kt | 33 +++ .../example/openapi/test/model/Mammal.kt | 63 ++++++ .../example/openapi/test/model/Reptile.kt | 71 +++++++ .../openapi/RequestBodyControllerTest.kt | 67 ++++++ .../SerdeJsonSubtypesKotlinSpec.groovy | 201 ++++++++++++++++-- 12 files changed, 652 insertions(+), 18 deletions(-) create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApi.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApiImpl.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Animal.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Bird.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/ColorEnum.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Error.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Mammal.kt create mode 100644 doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Reptile.kt create mode 100644 doc-examples/example-kotlin-ksp/src/test/kotlin/openapi/RequestBodyControllerTest.kt diff --git a/doc-examples/example-kotlin-ksp/build.gradle.kts b/doc-examples/example-kotlin-ksp/build.gradle.kts index fe7232dfc..8412bfcbd 100644 --- a/doc-examples/example-kotlin-ksp/build.gradle.kts +++ b/doc-examples/example-kotlin-ksp/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { runtimeOnly(mnLogging.logback.classic) testImplementation(mnTest.micronaut.test.junit5) + testImplementation(mnTest.junit.jupiter.params) } java { diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/Application.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/Application.kt index 1afbdaa0e..cc41dfba8 100644 --- a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/Application.kt +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/Application.kt @@ -6,11 +6,11 @@ import io.micronaut.serde.annotation.SerdeImport fun main(args: Array) { build() .args(*args) - .packages("com.example") + .packages("example") .start() } @SerdeImport( value = Product::class, mixin = ProductMixin::class) // <1> -class Serdes {} \ No newline at end of file +class Serdes {} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApi.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApi.kt new file mode 100644 index 000000000..bbdf87556 --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApi.kt @@ -0,0 +1,21 @@ +package example.openapi.test.api + +import io.micronaut.http.annotation.Body +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Put +import example.openapi.test.model.Animal + +@Controller +interface RequestBodyApi { + + /** + * A method to send a model with discriminator in body + * + * @param animal (required) + * @return Animal + */ + @Put("/sendModelWithDiscriminator") + fun sendModelWithDiscriminator( + @Body animal: Animal, + ): Animal +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApiImpl.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApiImpl.kt new file mode 100644 index 000000000..b134a9584 --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/api/RequestBodyApiImpl.kt @@ -0,0 +1,11 @@ +package example.openapi.test.api + +import io.micronaut.http.annotation.Controller +import example.openapi.test.model.Animal + +@Controller +class RequestBodyApiImpl : RequestBodyApi { + + override fun sendModelWithDiscriminator(animal: Animal): Animal = + animal +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Animal.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Animal.kt new file mode 100644 index 000000000..14e8fda1b --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Animal.kt @@ -0,0 +1,64 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.* +import io.micronaut.core.annotation.Nullable +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated +import java.util.* + +/** + * Animal + */ +@Serdeable +@JsonPropertyOrder( + Animal.JSON_PROPERTY_PROPERTY_CLASS, + Animal.JSON_PROPERTY_COLOR, +) +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +@JsonIgnoreProperties( + value = ["class"], // ignore manually set class, it will be automatically generated by Jackson during serialization + allowSetters = true, // allows the class to be set during deserialization +) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "class", visible = true) +@JsonSubTypes( + JsonSubTypes.Type(value = Bird::class, name = "ave"), + JsonSubTypes.Type(value = Mammal::class, name = "mammalia"), + JsonSubTypes.Type(value = Reptile::class, name = "reptilia"), +) +open class Animal( + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + open var propertyClass: String? = null, + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_COLOR) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + open var color: ColorEnum? = null, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Animal + return propertyClass == other.propertyClass + && color == other.color + } + + override fun hashCode(): Int = + Objects.hash(propertyClass, color) + + override fun toString(): String = + "Animal(propertyClass='$propertyClass', color='$color')" + + companion object { + + const val JSON_PROPERTY_PROPERTY_CLASS = "class" + const val JSON_PROPERTY_COLOR = "color" + } +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Bird.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Bird.kt new file mode 100644 index 000000000..951b2cc95 --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Bird.kt @@ -0,0 +1,76 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import io.micronaut.core.annotation.Nullable +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated +import java.math.BigDecimal +import java.util.* + +/** + * Bird + */ +@Serdeable +@JsonPropertyOrder( + Bird.JSON_PROPERTY_NUM_WINGS, + Bird.JSON_PROPERTY_BEAK_LENGTH, + Bird.JSON_PROPERTY_FEATHER_DESCRIPTION, +) +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +class Bird( + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_NUM_WINGS) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var numWings: Int? = null, + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_BEAK_LENGTH) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var beakLength: BigDecimal? = null, + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_FEATHER_DESCRIPTION) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var featherDescription: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + propertyClass: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_COLOR) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + color: ColorEnum? = null, +) : Animal(propertyClass, color) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Bird + return numWings == other.numWings + && beakLength == other.beakLength + && featherDescription == other.featherDescription + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(numWings, beakLength, featherDescription, super.hashCode()) + + override fun toString(): String = + "Bird(numWings='$numWings', beakLength='$beakLength', featherDescription='$featherDescription', propertyClass='$propertyClass', color='$color')" + + companion object { + + const val JSON_PROPERTY_NUM_WINGS = "numWings" + const val JSON_PROPERTY_BEAK_LENGTH = "beakLength" + const val JSON_PROPERTY_FEATHER_DESCRIPTION = "featherDescription" + } +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/ColorEnum.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/ColorEnum.kt new file mode 100644 index 000000000..7ffa0f2ce --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/ColorEnum.kt @@ -0,0 +1,58 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonValue +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated + +/** + * Gets or Sets ColorEnum + * + * @param value The value represented by this enum + */ +@Serdeable +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +enum class ColorEnum( + @get:JsonValue val value: String, +) { + + @JsonProperty("red") + RED("red"), + + @JsonProperty("blue") + BLUE("blue"), + + @JsonProperty("green") + GREEN("green"), + + @JsonProperty("light-blue") + LIGHT_BLUE("light-blue"), + + @JsonProperty("dark-green") + DARK_GREEN("dark-green"), + ; + + override fun toString(): String = value + + companion object { + + @JvmField + val VALUE_MAPPING = entries.associateBy { it.value.lowercase() } + + /** + * Create this enum from a value. + * + * @param value The value + * + * @return The enum + */ + @JsonCreator + @JvmStatic + fun fromValue(value: String): ColorEnum { + val key = value.lowercase() + require(VALUE_MAPPING.containsKey(key)) { "Unexpected value '$key'" } + return VALUE_MAPPING[key]!! + } + } +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Error.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Error.kt new file mode 100644 index 000000000..189dc96e6 --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Error.kt @@ -0,0 +1,33 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import io.micronaut.core.annotation.Nullable +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated + +/** + * An object for describing errors + */ +@Serdeable +@JsonPropertyOrder( + Error.JSON_PROPERTY_MESSAGE, +) +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +data class Error( + + /** + * The error message + */ + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_MESSAGE) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var message: String? = null, +) { + + companion object { + + const val JSON_PROPERTY_MESSAGE = "message" + } +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Mammal.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Mammal.kt new file mode 100644 index 000000000..355c3b54d --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Mammal.kt @@ -0,0 +1,63 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import io.micronaut.core.annotation.Nullable +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated +import java.util.* + +/** + * Mammal + */ +@Serdeable +@JsonPropertyOrder( + Mammal.JSON_PROPERTY_WEIGHT, + Mammal.JSON_PROPERTY_DESCRIPTION, +) +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +class Mammal( + + @field:JsonProperty(JSON_PROPERTY_WEIGHT) + var weight: Float, + + @field:JsonProperty(JSON_PROPERTY_DESCRIPTION) + var description: String, + + @Nullable + @JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + propertyClass: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_COLOR) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + color: ColorEnum? = null, +) : Animal(propertyClass, color) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Mammal + return weight == other.weight + && description == other.description + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(weight, description, super.hashCode()) + + override fun toString(): String = + "Mammal(weight='$weight', description='$description', propertyClass='$propertyClass', color='$color')" + + companion object { + + const val JSON_PROPERTY_WEIGHT = "weight" + const val JSON_PROPERTY_DESCRIPTION = "description" + } +} diff --git a/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Reptile.kt b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Reptile.kt new file mode 100644 index 000000000..563292f0a --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/main/kotlin/example/openapi/test/model/Reptile.kt @@ -0,0 +1,71 @@ +package example.openapi.test.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import io.micronaut.core.annotation.Nullable +import io.micronaut.serde.annotation.Serdeable +import jakarta.annotation.Generated +import java.util.* + +/** + * Reptile + */ +@Serdeable +@JsonPropertyOrder( + Reptile.JSON_PROPERTY_NUM_LEGS, + Reptile.JSON_PROPERTY_FANGS, + Reptile.JSON_PROPERTY_FANG_DESCRIPTION, +) +@Generated("io.micronaut.openapi.generator.KotlinMicronautServerCodegen") +class Reptile( + + @field:JsonProperty(JSON_PROPERTY_NUM_LEGS) + var numLegs: Int, + + @field:JsonProperty(JSON_PROPERTY_FANGS) + var fangs: Boolean, + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_FANG_DESCRIPTION) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var fangDescription: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + propertyClass: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_COLOR) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + color: ColorEnum? = null, +) : Animal(propertyClass, color) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Reptile + return numLegs == other.numLegs + && fangs == other.fangs + && fangDescription == other.fangDescription + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(numLegs, fangs, fangDescription, super.hashCode()) + + override fun toString(): String = + "Reptile(numLegs='$numLegs', fangs='$fangs', fangDescription='$fangDescription', propertyClass='$propertyClass', color='$color')" + + companion object { + + const val JSON_PROPERTY_NUM_LEGS = "numLegs" + const val JSON_PROPERTY_FANGS = "fangs" + const val JSON_PROPERTY_FANG_DESCRIPTION = "fangDescription" + } +} diff --git a/doc-examples/example-kotlin-ksp/src/test/kotlin/openapi/RequestBodyControllerTest.kt b/doc-examples/example-kotlin-ksp/src/test/kotlin/openapi/RequestBodyControllerTest.kt new file mode 100644 index 000000000..d3b48233b --- /dev/null +++ b/doc-examples/example-kotlin-ksp/src/test/kotlin/openapi/RequestBodyControllerTest.kt @@ -0,0 +1,67 @@ +package openapi + +import example.openapi.test.model.* +import io.micronaut.core.type.Argument +import io.micronaut.http.HttpRequest +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.runtime.server.EmbeddedServer +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import java.math.BigDecimal +import java.util.stream.Stream + +@MicronautTest +class RequestBodyControllerTest( + var server: EmbeddedServer, + @Client("/") + var reactiveClient: HttpClient, +) { + + lateinit var client: BlockingHttpClient + + @BeforeEach + fun setup() { + client = reactiveClient.toBlocking() + } + + @MethodSource("discriminators") + @ParameterizedTest + fun testSendModelWithDiscriminatorChild1(discriminatorName: String, model: Animal) { + val request = HttpRequest.PUT("/sendModelWithDiscriminator", model) + val response = client.retrieve(request, Argument.of(Animal::class.java), Argument.of(String::class.java)) + + assertEquals(discriminatorName, response.propertyClass) + + response.propertyClass = null + assertEquals(model, response) + + val stringResponse = client.retrieve(request, Argument.of(String::class.java)) + assertTrue(stringResponse.contains(""""class":"$discriminatorName"""")) + } + + companion object { + + @JvmStatic + fun discriminators(): Stream { + val bird = Bird(2, BigDecimal.valueOf(12, 1), "Large blue and white feathers") + bird.color = ColorEnum.BLUE + val mammal = Mammal(20.5f, "A typical Canadian beaver") + mammal.color = ColorEnum.BLUE + val reptile = Reptile(0, true, "A pair of venomous fangs") + reptile.color = ColorEnum.BLUE + return Stream.of( + arguments("ave", bird), + arguments("mammalia", mammal), + arguments("reptilia", reptile) + ) + } + } +} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesKotlinSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesKotlinSpec.groovy index 167ec7a36..7b033c509 100644 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesKotlinSpec.groovy +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesKotlinSpec.groovy @@ -75,20 +75,24 @@ package test import com.fasterxml.jackson.annotation.* import io.micronaut.core.annotation.Nullable import io.micronaut.serde.annotation.Serdeable +import jakarta.validation.constraints.* import java.math.BigDecimal +import java.util.Objects @Serdeable @JsonPropertyOrder( - Animal.JSON_PROPERTY_PROPERTY_CLASS, - Animal.JSON_PROPERTY_COLOR + Animal.JSON_PROPERTY_PROPERTY_CLASS, + Animal.JSON_PROPERTY_COLOR, ) @JsonIgnoreProperties( - value = ["class"], - allowSetters = true + value = ["class"], // ignore manually set class, it will be automatically generated by Jackson during serialization + allowSetters = true, // allows the class to be set during deserialization ) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "class", visible = true) @JsonSubTypes( - JsonSubTypes.Type(value = Bird::class, name = "ave") + JsonSubTypes.Type(value = Bird::class, name = "ave"), + JsonSubTypes.Type(value = Mammal::class, name = "mammalia"), + JsonSubTypes.Type(value = Reptile::class, name = "reptilia"), ) open class Animal( @@ -103,6 +107,24 @@ open class Animal( open var color: ColorEnum? = null, ) { + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Animal + return propertyClass == other.propertyClass + && color == other.color + } + + override fun hashCode(): Int = + Objects.hash(propertyClass, color) + + override fun toString(): String = + "Animal(propertyClass='\$propertyClass', color='\$color')" + companion object { const val JSON_PROPERTY_PROPERTY_CLASS = "class" @@ -144,36 +166,183 @@ class Bird( color: ColorEnum? = null, ) : Animal(propertyClass, color) { + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Bird + return numWings == other.numWings + && beakLength == other.beakLength + && featherDescription == other.featherDescription + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(numWings, beakLength, featherDescription, super.hashCode()) + + override fun toString(): String = + "Bird(numWings='\$numWings', beakLength='\$beakLength', featherDescription='\$featherDescription', propertyClass='\$propertyClass', color='\$color')" + companion object { - const val JSON_PROPERTY_NUM_WINGS = "numWings1" + const val JSON_PROPERTY_NUM_WINGS = "numWings" const val JSON_PROPERTY_BEAK_LENGTH = "beakLength" const val JSON_PROPERTY_FEATHER_DESCRIPTION = "featherDescription" } } +@Serdeable +@JsonPropertyOrder( + Mammal.JSON_PROPERTY_WEIGHT, + Mammal.JSON_PROPERTY_DESCRIPTION, +) +class Mammal( + + @field:NotNull + @field:JsonProperty(JSON_PROPERTY_WEIGHT) + var weight: Float, + + @field:NotNull + @field:JsonProperty(JSON_PROPERTY_DESCRIPTION) + var description: String, + + @Nullable + @JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + propertyClass: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_COLOR) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + color: ColorEnum? = null, +) : Animal(propertyClass, color) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Mammal + return weight == other.weight + && description == other.description + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(weight, description, super.hashCode()) + + override fun toString(): String = + "Mammal(weight='\$weight', description='\$description', propertyClass='\$propertyClass', color='\$color')" + + companion object { + + const val JSON_PROPERTY_WEIGHT = "weight" + const val JSON_PROPERTY_DESCRIPTION = "description" + } +} + +@Serdeable +@JsonPropertyOrder( + Reptile.JSON_PROPERTY_NUM_LEGS, + Reptile.JSON_PROPERTY_FANGS, + Reptile.JSON_PROPERTY_FANG_DESCRIPTION, +) +class Reptile( + + @field:NotNull + @field:JsonProperty(JSON_PROPERTY_NUM_LEGS) + var numLegs: Int, + + @field:NotNull + @field:JsonProperty(JSON_PROPERTY_FANGS) + var fangs: Boolean, + + @field:Nullable + @field:JsonProperty(JSON_PROPERTY_FANG_DESCRIPTION) + @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) + var fangDescription: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + propertyClass: String? = null, + + @Nullable + @JsonProperty(JSON_PROPERTY_COLOR) + @JsonInclude(JsonInclude.Include.USE_DEFAULTS) + color: ColorEnum? = null, +) : Animal(propertyClass, color) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + other as Reptile + return numLegs == other.numLegs + && fangs == other.fangs + && fangDescription == other.fangDescription + && super.equals(other) + } + + override fun hashCode(): Int = + Objects.hash(numLegs, fangs, fangDescription, super.hashCode()) + + override fun toString(): String = + "Reptile(numLegs='\$numLegs', fangs='\$fangs', fangDescription='\$fangDescription', propertyClass='\$propertyClass', color='\$color')" + + companion object { + + const val JSON_PROPERTY_NUM_LEGS = "numLegs" + const val JSON_PROPERTY_FANGS = "fangs" + const val JSON_PROPERTY_FANG_DESCRIPTION = "fangDescription" + } +} + @Serdeable enum class ColorEnum( - @get:JsonValue val value: String + @get:JsonValue val value: String, ) { @JsonProperty("red") - RED("red"); + RED("red"), + @JsonProperty("blue") + BLUE("blue"), + @JsonProperty("green") + GREEN("green"), + @JsonProperty("light-blue") + LIGHT_BLUE("light-blue"), + @JsonProperty("dark-green") + DARK_GREEN("dark-green"), + ; - override fun toString(): String { - return value - } + override fun toString(): String = value companion object { @JvmField - val VALUE_MAPPING = entries.associateBy { it.value } + val VALUE_MAPPING = entries.associateBy { it.value.lowercase() } + /** + * Create this enum from a value. + * + * @param value The value + * + * @return The enum + */ @JsonCreator @JvmStatic fun fromValue(value: String): ColorEnum { - require(VALUE_MAPPING.containsKey(value)) { "Unexpected value '\$value'" } - return VALUE_MAPPING[value]!! + val key = value.lowercase() + require(VALUE_MAPPING.containsKey(key)) { "Unexpected value '\$key'" } + return VALUE_MAPPING[key]!! } } } @@ -187,7 +356,7 @@ enum class ColorEnum( when: var result = deserializeFromString(jsonMapper, baseClass, """{ "class": "ave", - "numWings1": 2, + "numWings": 2, "beakLength": 12.1, "featherDescription": "this is description", "color": "red" @@ -202,7 +371,7 @@ enum class ColorEnum( var serialized = jsonMapper.writeValueAsString(result) then: - serialized == '{"class":"ave","numWings1":2,"beakLength":12.1,"featherDescription":"this is description","color":"red"}' + serialized == '{"class":"ave","numWings":2,"beakLength":12.1,"featherDescription":"this is description","color":"red"}' cleanup: Thread.currentThread().setContextClassLoader(cl)