Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KSP: Serde can't work with discrimantor #768

Open
altro3 opened this issue Feb 15, 2024 · 12 comments · Fixed by micronaut-projects/micronaut-core#10576
Open

KSP: Serde can't work with discrimantor #768

altro3 opened this issue Feb 15, 2024 · 12 comments · Fixed by micronaut-projects/micronaut-core#10576
Labels
lang: kotlin Issues or features specific to Kotlin

Comments

@altro3
Copy link
Contributor

altro3 commented Feb 15, 2024

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

@altro3
Copy link
Contributor Author

altro3 commented Feb 15, 2024

@dstepanov next bug from micronaut-openapi :-)

@altro3
Copy link
Contributor Author

altro3 commented Feb 15, 2024

@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

@altro3 altro3 changed the title Kotlin: Serde can't work with discrimantor KSP: Serde can't work with discrimantor Feb 15, 2024
@altro3
Copy link
Contributor Author

altro3 commented Feb 15, 2024

Also can say that with io.micronaut:micronaut-jackson-databind all works fine

@altro3
Copy link
Contributor Author

altro3 commented Mar 8, 2024

@dstepanov You close it too early. Still have the same error:
изображение

@altro3
Copy link
Contributor Author

altro3 commented Mar 21, 2024

@dstepanov Please, reopen issue. It still doesn't work :-(

@graemerocher graemerocher reopened this Mar 21, 2024
@altro3
Copy link
Contributor Author

altro3 commented Mar 31, 2024

@dstepanov Hi! Any news about this?

@dstepanov
Copy link
Contributor

Nope, need to check again

@sdelamo sdelamo added the lang: kotlin Issues or features specific to Kotlin label Apr 19, 2024
@dstepanov
Copy link
Contributor

@altro3 Looks like @field:JsonProperty(JSON_PROPERTY_PROPERTY_CLASS) is needed

@altro3
Copy link
Contributor Author

altro3 commented Apr 29, 2024

@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 - #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

@altro3
Copy link
Contributor Author

altro3 commented Apr 29, 2024

@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"
    }
}

@altro3
Copy link
Contributor Author

altro3 commented Apr 29, 2024

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 Birdobject is:

изображение

As you see, I don't set field propertyClass. This value calculated by micronaut-serde

@altro3
Copy link
Contributor Author

altro3 commented Apr 29, 2024

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

altro3 added a commit to altro3/micronaut-serialization that referenced this issue Jun 21, 2024
dstepanov pushed a commit to altro3/micronaut-serialization that referenced this issue Nov 29, 2024
dstepanov pushed a commit to altro3/micronaut-serialization that referenced this issue Nov 29, 2024
dstepanov pushed a commit to altro3/micronaut-serialization that referenced this issue Jan 8, 2025
dstepanov added a commit that referenced this issue Jan 8, 2025
* Add test for process KSP Json subtypes. See #768

* Correct test

* Revert `Fix test`

* Added new test with constructor argument annotations

* Added new integration test for #875

* Use latest core

---------

Co-authored-by: Denis Stepanov <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lang: kotlin Issues or features specific to Kotlin
Projects
No open projects
Status: No status
Development

Successfully merging a pull request may close this issue.

4 participants