Spring @RequestBody and Jackson custom polymorphic deserialization

Viewed 448

Suppose I have some @RestController with a @PostMapping like this:

@PostMapping
public SomeResponse create(@RequestBody AbstractRequest request) {
    ...
}

and this AbstractRequest is an abstract class extended by ConcreteOneRequest and ConcreteTwoRequest classes:

@Value
@SuperBuilder
public ConcreteOneRequest extends AbstractRequest {
    String concreteOneUniqueField;
}

@Value
@SuperBuilder
public ConcreteTwoRequest extends AbstractRequest {
    String concreteTwoUniqueField;
}

Based on the above structure how can I use @JsonTypeInfo and/or @JsonSubTypes and/or @JsonTypeResolver to deserialize to the proper concrete class?

I have read the Jackson JavaDocs but I cannot understand their usage, I've also searched for specific examples but none I found matched my requirements.

Please help, thanks in advance.

1 Answers

In AbstractRequest you would add the following class-level annotations:

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = AbstractRequest.DISCRIMINATOR
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ConcreteOneRequest.class, name = FeeDefinitionType.Discriminator.TIERED),
        @JsonSubTypes.Type(value = ConcreteTwoRequest.class, name = FeeDefinitionType.Discriminator.STANDARD),
})

Where @JsonTypeInfo defines the polymorphic deserialization settings. Here, the important part is the DISCRIMINATOR property, which will be a String located in AbstractRequest.

It will tell Jackson to look for this key in the API request and deserialise the different object instances depending on its value.

You will define a getter for it, so the child classes can provide their discriminating values.

In @JsonSubTypes you indicate the list of possible sub-types for deserialization.

Here is a complete example:

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = AbstractRequest.DISCRIMINATOR
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ConcreteOneRequest.class, name = ConcreteOneRequest.getType()),
        @JsonSubTypes.Type(value = ConcreteTwoRequest.class, name = ConcreteTwoRequest.getType())
})
public abstract class AbstractRequest {
    public static final String DISCRIMINATOR = "type";

    abstract String getType();

}

So when ConcreteOneRequest returns "CONCRETE_REQUEST_ONE" on the getType(), Jackson will attempt to deserialise it when the type key in the API request has this value.

Related