Component | Version |
---|---|
Java Development Kit | JDK 17 LTS (Long-term Support) |
JSON Binding Specification | Jakarta JSON Binding 2.0.0 (part of Jakarta EE 9) |
JSON Binding Implementation | Eclipse Yasson 2.0.2 |
Build Automation Tool | Apache Maven 3.6.3+ |
Java Records are great match for representing DTO in REST communication. This project explores what is the minimal setup for successful serialization and deserialization of Java Records to and from JSON representation.
Adam Bien's blog post about this topic: https://adambien.blog/roller/abien/entry/serializing_and_deserializing_java_records
Since Java Records are immutable the JSON-B implementation cannot use default constructor to create empty instance and then set the properties using fields or methods. All values have to be provided at once. We can use static initializers as it was shown in Adam's blog post or constructor. It is particularly useful to mark record's compact constructor with @JsonbCreator annotation. This way we don't need to repeat record components again as parameters.
public record Workshop(String title, LocalDateTime date, String description) {
@JsonbCreator
public Workshop {}
}
There are some limitation to this approach:
-
Absent JSON fields that could represent
null
or some default values in Java objects cannot be used with current JSON-B specification (see section 4.5 Custom instantiation).In case a field required for a parameter mapping doesn’t exist in JSON document, JsonbException MUST be thrown.
This is unfortunate in case when we don't have control over consumed JSON. This problem is well-known and it is discussed for example here: jakartaee/jsonb-api#121
Solution should be part of Pull Request: jakartaee/jsonb-api#285 and it seems will be part of Jakarta JSON Binding 2.1.0. It seems that change of annotation over compact constructor will help:
@JsonbCreator(parameters = JsonbCreator.ParameterState.OPTIONAL) public Workshop {}
-
Static code analyzers may complain about the code because the constructor body is empty and the canonical constructor is implicitly created. Currently, SonarQube is complaining with two code smells (
java:S1186
andjava:S6207
) for the same line with compact constructor.
For serialization purposes it is needed to setup configuration so records fields are used to get values instead of accessor methods.
JsonbConfig config = new JsonbConfig()
.withPropertyVisibilityStrategy(new org.eclipse.yasson.FieldAccessStrategy());
The property visibility strategy was taken from Yasson itself. I think it simplified the example, but I don't recommend this approach. The code depends on the implementation
and that could be the problem if switch to another implementation would be needed in the future.
There are two possible ways that could solve this problem:
- Future version of JSON-B could detect that class is actually record and use proper strategy by default;
- Common implementations of strategies should be part of spec in the future (currently in 2.x Milestone) - see jakartaee/jsonb-api#164
Current state is usable for cases when absent field in JSON can be eliminated. New version of specification should fix this and the situation will be ideal for Java Records as DTOs. The problem with strategies is minor because we can always create own implementation in our code without referencing Yasson implementation.