micronaut-serialization
micronaut-serialization copied to clipboard
KSP: Serde can't work with discrimantor
Get a sample application: https://github.com/altro3/micronaut3-bug/tree/kotlin (branch kotlin
)
Send simple request to endpoint localhost:8080/test
{
"numWings": 2,
"beakLength": 12.1,
"featherDescription": "this is description",
"class": "ave",
"color": "red"
}
And you will see exception:
Sample controller:
@Post("/test")
open fun test(@Body @NotNull @Valid animal: Animal): Mono<Animal> {
return Mono.just(animal)
}
Sample model classes:
@Serdeable
@JsonPropertyOrder(
Animal.JSON_PROPERTY_PROPERTY_CLASS,
Animal.JSON_PROPERTY_COLOR
)
@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")
)
open class Animal(
@Nullable
@JsonProperty(JSON_PROPERTY_COLOR)
@JsonInclude(JsonInclude.Include.USE_DEFAULTS)
open var color: ColorEnum? = null,
@Nullable
@JsonProperty(JSON_PROPERTY_PROPERTY_CLASS)
@JsonInclude(JsonInclude.Include.USE_DEFAULTS)
open var propertyClass: String? = null,
) {
companion object {
const val JSON_PROPERTY_PROPERTY_CLASS = "class"
const val JSON_PROPERTY_COLOR = "color"
}
}
Bird
@Serdeable
@JsonPropertyOrder(
Bird.JSON_PROPERTY_NUM_WINGS,
Bird.JSON_PROPERTY_BEAK_LENGTH,
Bird.JSON_PROPERTY_FEATHER_DESCRIPTION
)
data class Bird(
@Nullable
@JsonProperty(JSON_PROPERTY_NUM_WINGS)
@JsonInclude(JsonInclude.Include.USE_DEFAULTS)
var numWings: Int? = null,
@Nullable
@JsonProperty(JSON_PROPERTY_BEAK_LENGTH)
@JsonInclude(JsonInclude.Include.USE_DEFAULTS)
var beakLength: BigDecimal? = null,
@Nullable
@JsonProperty(JSON_PROPERTY_FEATHER_DESCRIPTION)
@JsonInclude(JsonInclude.Include.USE_DEFAULTS)
var featherDescription: String? = null,
) : Animal() {
companion object {
const val JSON_PROPERTY_NUM_WINGS = "numWings"
const val JSON_PROPERTY_BEAK_LENGTH = "beakLength"
const val JSON_PROPERTY_FEATHER_DESCRIPTION = "featherDescription"
}
}
ColorEnum
@Serdeable
enum class ColorEnum(
@get:JsonValue val value: String
) {
@JsonProperty("red")
RED("red");
override fun toString(): String {
return value
}
companion object {
@JvmField
val VALUE_MAPPING = entries.associateBy { it.value }
@JsonCreator
@JvmStatic
fun fromValue(value: String): ColorEnum {
require(VALUE_MAPPING.containsKey(value)) { "Unexpected value '$value'" }
return VALUE_MAPPING[value]!!
}
}
}
Exception reproduce only with KSP.
IMPRTANT: For this problem it doesn' matter do you use field:
prefix or not. It doesn't work in both cases
Example Application
https://github.com/altro3/micronaut3-bug/tree/kotlin
Version
4.3.5
@dstepanov next bug from micronaut-openapi :-)
@dstepanov problem only with KSP, with java and KAPT all works fine. You can check java in the same scenario and compare introspection in the same project in branch main
: https://github.com/altro3/micronaut3-bug/tree/main
Also can say that with io.micronaut:micronaut-jackson-databind
all works fine
@dstepanov You close it too early. Still have the same error:
@dstepanov Please, reopen issue. It still doesn't work :-(
@dstepanov Hi! Any news about this?
Nope, need to check again
@altro3 Looks like @field:JsonProperty(JSON_PROPERTY_PROPERTY_CLASS)
is needed
@dstepanov No, problem not here. I try to generate classes with prefix field
, and now I see wrong value in propertyClass
field:
As you see, discriminator value must be ave
, but we see className in this field - Bird
.
I tried write test for KSP here - https://github.com/micronaut-projects/micronaut-serialization/pull/833 , but every time I see problem with not found Introspected class, but in temp directory I see it.
In my opinion, you need to write a test for this case and understand what the problem is with KSP.
@dstepanov But without your help I won't be able to do this
@dstepanov Interesting difference between introspection for KSP and KAPT (left is KSP):
Intropected class (the same for KAP and KSP):
@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:Schema(name = "color", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@field:JsonProperty(JSON_PROPERTY_COLOR)
@field:JsonInclude(JsonInclude.Include.USE_DEFAULTS)
open var color: ColorEnum? = null,
@field:Nullable
@field:Schema(name = "class", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@field:JsonProperty(JSON_PROPERTY_PROPERTY_CLASS)
@field:JsonInclude(JsonInclude.Include.USE_DEFAULTS)
open var propertyClass: String? = null,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Animal
if (propertyClass != other.propertyClass) return false
if (color != other.color) return false
return true
}
override fun hashCode(): Int {
var result = propertyClass?.hashCode() ?: 0
result = 31 * result + (color?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "Animal(propertyClass='$propertyClass', color='$color')"
}
companion object {
const val JSON_PROPERTY_PROPERTY_CLASS = "class"
const val JSON_PROPERTY_COLOR = "color"
}
}
It became clear why if we add the field
prefix with KSP, we have the wrong Bird value. The problem is in object serialization when requested:
As you see, object Bird
with serialization set value Bird
to class
property. Must be ave
My test Bird
object is:
As you see, I don't set field propertyClass
. This value calculated by micronaut-serde
Problem in class SerBean
:
As you see, you set value
for decriptor annotaion value SerdeConfig.TYPE_NAME
. SerdeConfig.TYPE_NAME
annotaion value is a class name. I don't know why. Maybe problem is before and we have the wrong value in tis annotaion value.
I am 100% sure that we need a test, because there are at least 2 bugs for KSP