micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.01k stars 1.05k forks source link

Subtype of a Controller return type didn't work anymore since Micronaut 4 #10294

Open loicmathieu opened 7 months ago

loicmathieu commented 7 months ago

Expected Behavior

From a controller, when we return a subtype of the declared type, the response should contain the properties of the subtype.

For example with the following controller

@io.micronaut.http.annotation.Controller("/example")
public class Controller {

    @Get(uri = "/")
    public Example example() {
        var example = new ExampleExtended();
        example.setProps1("props1");
        example.setProps2("props2");
        return example;
    }

    @Introspected
    @Serdeable
    public static class Example {
        private String props1;

        public String getProps1() {
            return props1;
        }

        public void setProps1(String props1) {
            this.props1 = props1;
        }
    }

    @Introspected
    @Serdeable
    public static class ExampleExtended extends Example {
        private String props2;

        public String getProps2() {
            return props2;
        }

        public void setProps2(String props2) {
            this.props2 = props2;
        }
    }
}

The response should be

{
  "props1": "props1",
  "props2": "props2"
}

Actual Behaviour

Starting with Micronaut 4, the response didn't include the properties from the subtype anymore.

The response from the previous example is now

{
  "props1": "props1"
}

Fun fact, if I use Object as a return type it works. It of course works if I use ExampleExtended as a return type but on my real world usage I cannot as I need to return the declared type or a subtype based on some query param.

Steps To Reproduce

Here is a reproducer with Micronaut 4.2.1 but we reproduce it also with 4.0.7 which is the version we try to migrate to. controller-polymorphism.zip

Environment Information

OS: Linux *** #14-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 14 14:59:49 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux Java: OpenJDK Runtime Environment Temurin-17.0.8.1+1 (build 17.0.8.1+1)

Example Application

No response

Version

4.0.7, 4.2.1

graemerocher commented 7 months ago

can you try:

class Controller<T extends Example> {
@Get(uri = "/")
    public T example() {
        var example = new ExampleExtended();
        example.setProps1("props1");
        example.setProps2("props2");
        return example;
    }

}

Not sure if we should fix this personally, using only the static types to compute serialisers is better for performance.

loicmathieu commented 7 months ago

It didn't work.

Not sure if we should fix this personally, using only the static types to compute serialisers is better for performance.

Well, performance is one thing, and usability is another. I'm really surprised that nobody has this issue before me as it's very common to have a method using subtypes of the declaring return type. This is Java OO basics.

yawkat commented 6 months ago

Yes this is the intended behavior as of mn 4. We serialize using the static type now, except for types such as Object or I believe when there's @JsonSubTypes