javaspring-bootjackson

@JsonUnwrapped using generic inheritance


I tried everything but I'm still stuck on this. I'm using the jackson annotation @JsonUnwrapped to flatten my request while using inheritance and generic types at the same time. The fact is that I don't want to use a custom deserializer.

Reproduction step :

{
  "bar1": "12",
  "bar2": "bar2",
  "foo1": "foo1",
  "foo2": "123"
}
@Slf4j
@Validated
@RestController
@RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {

    @PostMapping("/bar")
    public void test(@Valid @RequestBody final Bar1 bar1) {
        log.info("{}", bar1);
    }
}
public abstract class AbstractBar<F extends AbstractFoo> {

    @Pattern(regexp = "\\d{2}")
    protected final String bar1;

    @Valid
    @JsonUnwrapped
    protected final F foo;

    protected AbstractBar(final String bar1, final F foo) {
        this.bar1 = bar1;
        this.foo = foo;
    }

    public String getBar1() {
        return this.bar1;
    }

    public F getFoo() {
        return this.foo;
    }
}
public abstract class AbstractFoo {

    protected final String foo1;

    @Pattern(regexp = "\\d{3}")
    protected final String foo2;

    protected AbstractFoo(final String foo1,
                       final String foo2) {
        this.foo1 = foo1;
        this.foo2 = foo2;
    }

    public String getFoo1() {
        return this.foo1;
    }

    public String getFoo2() {
        return this.foo2;
    }
}
public class Bar1 extends AbstractBar<Foo1> {

    @NotBlank
    private final String bar2;

    @JsonCreator
    public Bar1(final String bar1,
                final String bar2,
                final String foo1,
                final String foo2) {
        super(bar1, new Foo1(foo1, foo2));
        this.bar2 = bar2;
    }

    @Override
    public @NotBlank String getBar1() {
        return this.bar1;
    }

    @Override
    public @NotNull Foo1 getFoo() {
        return this.foo;
    }
}
public class Foo1 extends AbstractFoo {

    public Foo1(final String foo1,
                final String foo2) {
        super(foo1, foo2);
    }

    @Override
    public @NotBlank String getFoo1() {
        return this.foo1;
    }

    @Override
    public @Nullable String getFoo2() {
        return this.foo2;
    }
}

When I send the json, my Bar1 JsonCreator is well understood

Bar1 construcor

But entering the getter, i get null values which ends as constraint validation error through my Foo1 @NotBlank on foo1 field

Bar1 getFoo

I'm using spring boot 3.4.0 which means jackson 2.18.1 dependencies. Thank you very much for your time ❤


Solution

  • The field F foo has been set by the constructor protected AbstractBar(final String bar1, final F foo), and then Jackson sets it again (using FieldProperty.deserializeAndSet), at this time, the fields of Foo1 are all null. You can use @JsonProperty(access = JsonProperty.Access.READ_ONLY) on the field to avoid it.

    UPDATE

    Another way is to remove final, JsonCreator, constructor, and then use the default constructor and setter.

    Note: Adding the annotation JsonCreator to the constructor of Foo1 does not work either, it seems that the data has been read and cannot be read again.