micronaut-projects / micronaut-serialization

Build Time Serialization APIs for Micronaut
Apache License 2.0
26 stars 18 forks source link

Using a sealed interface as a property in a request body seem to fail with "missing constructor" #827

Open Klaboe opened 6 months ago

Klaboe commented 6 months ago

Issue description

I am not sure if this is a bug or if i am doing something stupid; but here goes.

First some dependecies:

//DEPS io.micronaut:micronaut-http-server-netty:4.5.1
//DEPS io.micronaut:micronaut-http-client:4.5.1
//DEPS io.micronaut:micronaut-inject-java:4.5.1
//DEPS io.micronaut.serde:micronaut-serde-jackson:2.10.1

I have created a PATCH endpoint which accepts a List of patch operations as its RequestBody:

@Patch("/entity/{id}")
@Consumes(APPLICATION_JSON_PATCH)
public SomeResponse update(@PathVariable UUID id, @Body List<PatchRequest> patchOperations) {

A PatchRequest is a simple record where one of the properties, entity, is a sealed interface, Patchable, that can be one of two types; PossibleEntityRequest1 or PossibleEntityRequest2:

@Serdeable
public record PatchRequest(
    PatchOperation op,
    Patchable entity
) { }

Patchable looks like this:

@Serdeable
public sealed interface Patchable permits PossibleEntityRequest1, PossibleEntityRequest2 { }

PossibleEntityRequest1 and PossibleEntityRequest2 are the two possible entity-inputs that should be used when patching/updating an entity and is also just records with some data-properties

When calling the endpoint, all i get is

[
  {
     "message":"Failed to convert argument [patchOperations] for value [null] due to: Unable to deserialize type [Patchable entity]: No default constructor exists","path":"/patchOperations"}]},
     "message":"Bad Request"
  }
]

I have tried using

@JsonTypeInfo(
    use=Id.NAME,
    include=As.WRAPPER_OBJECT,
    property = "type")
@JsonSubTypes({
    @Type(PossibleEntityRequest1.class),
    @Type(PossibleEntityRequest2.class)
})

on either the interface it self or on the entity property but i still get the same error.

Thoughs?

EDIT 12.06.2024

Klaboe commented 5 months ago

Retested with with micronaut 4.5.1 and micronaut-serde-jackson:2.10.1 - No change

dstepanov commented 5 months ago

Please create an example.

The correct way is to add @JsonTypeInfo to the interface. This case might be different, looks like you are trying to convert null into a list, is that correct?

Klaboe commented 5 months ago

No i am trying to get entity to be serialized into either PossibleEntityRequest1 or PossibleEntityRequest2, since that is what the sealed interface allows. Not sure how you see this as trying to get null to become a list 😅

I have attached a project that is the bare minumum and that produces the same error: micronaut-serialization-issue-827.zip It has the @JsonTypeInfo on the interface

To trigger it, run the server and run simple

curl -X POST -H "Content-Type:application/json" http://localhost:8080/ -d '[ { "operation": { "r1": "R1" } } ]'

This will give the error "Failed to convert argument [operations] for value [null] due to: Unable to deserialize type [interface sealed.inter.face.issue.SealedInterface]: No default constructor exists","path":"/operations"

It is highly likly that i just dont know how to use @JsonTypeInfo

Klaboe commented 2 months ago

I see that i have used POST instead of PATCH here, but it should noe matter i think.

Does it illuminate the problem? Can you reproduce it?

I have retested with 2.11 (micronaut 4.6.1) and i see no change