Quarkus: Value of @JsonProperty on a field inside a subclass of PanacheEntityBase is ignored

Created on 21 Aug 2020  路  14Comments  路  Source: quarkusio/quarkus

Describe the bug
Having a class extend PanacheEntityBase and annotating a (column) field with @JsonProperty("something_else") (so as to rename the field to something_else in json) does not work. The value in JsonProperty gets ignored and the name of the field is used.
Sample class

public class Something extends PanacheEntityBase {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    public long id;
    @Column(name = "something_else") @JsonProperty("something_else")
    public String somethingElse;
}

Expected behavior
given the sample class, I would expect a json similar to the format:

{"id": 5, "something_else": "this is something else"}

Actual behavior
the actual json generated from jackson is similar to the format:

{"id": 5, "somethingElse": "this is something else"}
arepanache kinbug

Most helpful comment

Turned out to be an interesting bug :).

The issue surfaces because Panache Entities have @XmlTransient added to their fields and explicit @JsonProperty added to their generated getters (if you are interested in seeing this in action, you can open the .class file of your Model class from that is present in the -runner jar). The bug was that @JsonProperty of the generated getters didn't have the proper value - basically your value wasn't propagated to it.

Furthermore, this only showed up when the JAX-B Jackson module was enabled because Jackson seems to be smart enough to combine the field and method annotations when both are "active" (which is not the case when the JAX-B is enabled due the @XmlTransient field annotation I mentioned above)

I opened https://github.com/quarkusio/quarkus/pull/13271 which takes care of the issue.

All 14 comments

/cc @FroMage, @loicmathieu

@geoand

I'll check it out and let you know

I just tried this on one of our quickstarts (basically I changed this quickstart) to use:

@Entity
public class Room extends PanacheEntityBase {

    @PlanningId
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @NotNull
    private Long id;

    @NotBlank
    @JsonProperty("nm")
    @Column(name = "nm")
    private String name;

    public Room() {
    }

    public Room(String name) {
        this.name = name.trim();
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

}

and the resulting json output from calling http://localhost:8080/rooms was:

[

    {
        "id": 1,
        "nm": "Room A"
    },
    {
        "id": 2,
        "nm": "Room B"
    },
    {
        "id": 3,
        "nm": "Room C"
    }

]

So I can't reproduce the problem, at least not with 1.7.5.Final, 1.8.3.Final, 1.9.2.Final and 1.10.CR1.

Do you perhaps have a small reproducer that exhibits the behavior you report in this issue?

Thanks

OK, I've checked and you're right.
In order to reproduce the issue, I needed to add a customizer on the ObjectMapper, similar to:

@Singleton
public class JsonMapperCustomizer implements ObjectMapperCustomizer {
    @Override
    public void customize(ObjectMapper objectMapper) {
        objectMapper.findAndRegisterModules()
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
       ;
    }
}

So, the issue exists when the object mapper has been customized (tested on current latest version: 1.9.2.Final)

Interesting! I'll check it out.

I added the JsonMapperCustomizer and I still couldn't reproduce the problem.

If you find some time, it would be great to put together a minimal reproducer that we could you to reproducer and ultimately debug the problem.

Perhaps I missed something while trying to isolate the case.
I pushed the test code I managed to get having this error in this repository: https://github.com/JChrist/quarkus11505

The ExampleResourceTest reproduces the issue, on line: https://github.com/JChrist/quarkus11505/blob/master/src/test/java/gr/jchrist/ExampleResourceTest.java#L33
The returned json does not contain "something_else", as it contains "somethingElse".

I located the problem, and it's actually not a Quarkus specific issue.

In your JsonMapperCustomizer, you do: objectMapper.findAndRegisterModules().
This makes Jackson search the classpath for implementations of com.fasterxml.jackson.databind.Module and in your case among the others it finds, it also finds and registers JaxbAnnotationModule which really messes things up.

If you instead do:

    @Override
    public void customize(ObjectMapper objectMapper) {
        objectMapper
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

(notice I removed the call to findAndRegisterModules), things should work just fine.

Can you give it a shot let me know how it goes?

Yes, that mostly does it!
However, RestAssured's automatic json conversion seems to still be affected here: https://github.com/JChrist/quarkus11505/blob/master/src/test/java/gr/jchrist/ExampleResourceTest.java#L29

Replacing that line from:
given().when().body(m)
to:
given().when().body(mapper.writeValueAsString(m))
fixes it.

Still, objectMapper.findAndRegisterModules() call does not affect the json output when using POJOs (i.e. without Panache).

Yes, that mostly does it!
However, RestAssured's automatic json conversion seems to still be affected here: https://github.com/JChrist/quarkus11505/blob/master/src/test/java/gr/jchrist/ExampleResourceTest.java#L29

Replacing that line from:
given().when().body(m)
to:
given().when().body(mapper.writeValueAsString(m))
fixes it.

I am not sure how RestAssured sets up its ObjectMapper, but it's entirely possible that it makes that call automatically.

Still, objectMapper.findAndRegisterModules() call does not affect the json output when using POJOs (i.e. without Panache).

I would be very very surprised if that were the case. Do you have an example of this (in the same application I mean, as a different application may very well not have the problematic Jackson module on the classpath)?

Updated the repo to include a Model2 following the Java Bean pattern and separate endpoints and test.
With the Model2 way, everything works OOTB (including RestAssured).

Turned out to be an interesting bug :).

The issue surfaces because Panache Entities have @XmlTransient added to their fields and explicit @JsonProperty added to their generated getters (if you are interested in seeing this in action, you can open the .class file of your Model class from that is present in the -runner jar). The bug was that @JsonProperty of the generated getters didn't have the proper value - basically your value wasn't propagated to it.

Furthermore, this only showed up when the JAX-B Jackson module was enabled because Jackson seems to be smart enough to combine the field and method annotations when both are "active" (which is not the case when the JAX-B is enabled due the @XmlTransient field annotation I mentioned above)

I opened https://github.com/quarkusio/quarkus/pull/13271 which takes care of the issue.

Was this page helpful?
0 / 5 - 0 ratings