Java 14 record type and JSON-B, what is the current status?
Java 14 brings a new experimental feature: records. Records are a new "class" type which could be presented as "value" classes. For scala developpers, it is close to case classes or for java developpers it is the @Data of lombok but without the POJO convention. Let see how it integrates with JSON-B.
A record is as simple as a class with an implicit constructor:
record Foo(int property1, String property2) {}
You can also decorate the properties:
record Foo(int property1, @JsonbProperty("bar") String property2) {}
The first step to compile this sample is to add the flag --enable-preview to javac (and we will need to do the same to the java command). For maven it can look like:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>14</release>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<compilerArgs>
<compilerArg>--enable-preview</compilerArg>
</compilerArgs>
</configuration>
</plugin>
If you compile and decompile the record type, you will likely get this:
final class com.github.rmannibucau.record.Foo extends java.lang.Record { // 1
public com.github.rmannibucau.record.Foo(int, java.lang.String); // 2
// 3
public int property1();
public java.lang.String property2();
// 4
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
}
- Record base class just enforces a contract but is not super useful by itself in the context of this post (but it is for libraries, see 4.)
- A cnstructor is generated for the properties of the class
- An accessor is generated per property but it does not respect POJO convention, it uses the same name than the property - note that properties
- Record contract is implemented enforcing toString(), hashCode() and equals() to be implemented automatically (this is what can be useful for libraries, ensuring these methods are there open several doors).
- Something not that visible in the snippet is that parameters of the constructor are named - first parameter is named property1 and not arg0 as usual - which will be key for us.
The issue is obvious: default JSON-B mapping does not work since there is no getter and there is no default constructor so serialization AND deserialization will not work.
For the deserialization you can add @JsonbCreator on the constructor like that:
record Foo(int property1, String property2) {
@JsonbCreator
public Foo { // the implicit constructor is called anyway to enforce this "value" behavior
}
}
At this stage, it enables to deserialize the record - even if it broke the simplicity of record classes.
For the serialization, you can have the same kind of trick: add getters....but then you get a POJO implementing record constract (which is a bit better but will not help with the verbosity).
The last trick you will hit is the renaming of properties. We saw we can add @JsonbProperty on the properties, this will work for the fields but not the constructor so deserialization will not respect that.
So are we condamned to use POJO? Yes and no :). Today, if you want to be portable, you will need to. However, next release of Apache Johnzon supports out of the box record types so you can just write:
record Foo(int property1, @JsonbProperty("bar") String property2) {}
With that, if you run this code:
final var foo = new Foo(1, "foo");
try (final var jsonb = JsonbBuilder.create()) {
System.out.println("json=" + jsonb.toJson(foo)); // 1
System.out.println("object=" + jsonb.fromJson("{\"bar\":\"bar\",\"property1\":3}", Foo.class)); // 2
}
It will output:
json={"bar":"foo","property1":1} // 1
object=Foo[property1=3, property2=bar] // 2
- The serialization mapped both properties of the record and used the inline renaming of property2 as bar,
- The deserialization filled both properties, respecting the renaming of properties2 which was named bar in the JSON
So this shows that record classes will likely need some JSON-B specification itnegration but that there is no technical blocker to make it work.
This is a big promise for future java versions since it will enable to write more fluently anemic structures (API DTO for instance) but also to use pattern/expression matching more easily in our code.
From the same author:
In the same category: