Jump to the examples here!
Issues
In modern Java stack Lombok and Jackson are the most widely used frameworks and as those help to work with DTO there are a bunch of questions and problems might come for the library users.
There are 631 questions on StackOverflow by the search query https://stackoverflow.com/search?q=lombok+jackson
on the date of writing this article.
Definitely, a major part of this is specific problems but still a huge part of questions and answers leads to high diversity of examples, annotations usage. Following which, developers spread around repositories very different sets of annotations and configurations. It leaves lack of standards and best practices.
Expectations
- Annotations are uniform within different cases
- Jackson serializes and deserialises objects:
- mutable
- immutable
- Jackson serializes and deserialises objects of classes that are bounded by inheritance
- Classes contains as less Jackson annotations as possible to keep it framework agnostic and cleaner
Implementation
Inheritance case
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.NonFinal;
import lombok.experimental.SuperBuilder;
@Value
@NonFinal
@SuperBuilder
@RequiredArgsConstructor
public class Card {
String name;
Status status;
Balance balance;
}
import java.beans.ConstructorProperties;
import java.math.BigDecimal;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.SuperBuilder;
@Value
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
public class CreditCard extends Card {
BigDecimal rate;
@ConstructorProperties({"name", "status", "balance", "rate"})
public CreditCard(String name, Status status, Balance balance, BigDecimal rate) {
super(name, status, balance);
this.rate = rate;
}
}
Verifying by simple tests:
@Test
void itSerializes() throws Exception {
CreditCard creditCard = CreditCard
.builder()
.name("The credit card")
.rate(new BigDecimal("5.0"))
.status(ACTIVE)
.balance(Balance
.builder()
.id(1L)
.value(new BigDecimal("1000.0"))
.build())
.build();
String expectedJson = readFromFile("credit-card-serialize.json");
String actualJson = objectMapper.writeValueAsString(creditCard);
JSONAssert.assertEquals(expectedJson, actualJson, true);
}
@Test
void itDeserializes() throws Exception {
String inputJson = readFromFile("credit-card-deserialize.json");
CreditCard actual = objectMapper.readValue(inputJson, CreditCard.class);
assertEquals(new BigDecimal("5.0"), actual.getRate());
assertEquals("The credit card", actual.getName());
assertSame(ACTIVE, actual.getStatus());
assertNotNull(actual.getBalance());
assertEquals(1L, actual.getBalance().getId());
assertEquals(new BigDecimal("1000.0"), actual.getBalance().getValue());
}
Mutable with inheritance
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@NoArgsConstructor
public class Animal {
private Long id;
private String description;
}
@Data
@SuperBuilder
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Cat extends Animal {
private Integer lives;
}
Conclusions
In order to keep your objects immutable and be in the best compliance with Jackson use annotations:
lombok.Value
-
java.beans.ConstructorProperties
In order to keep object creation clean in DSL style with explicit fields mapping use annotations:
-
lombok.experimental.SuperBuilder
on both parent and children classes
CreditCard
.builder()
.rate(new BigDecimal("5.0"))
.name("The credit card") //parent field
.status(ACTIVE)
.balance(Balance.builder()
.id(1L)
.value(new BigDecimal("1000.0"))
.build())
.build()
I believe using a standard set of annotations within your project will simplify the maintenance. Reducing the amount of dependencies such as Jackson annotation will make it framework agnostic and plain tests will help you to verify the expected behaviour without a need to run the costly, slow tests.
Focus on:
- immutability
- tests
- uniformity
The GitHub project with examples
Top comments (3)
If you want to get rid of the ConstructorProperties annotation as well, check out the Moonwlker library.
@bertilmuth that one is great actually, exactly the right follow up out of my thoughts on the topic.
I'm glad you like it. It works well with Lombok, too. When you use them together, you can write very compact, record like value objects, like this one.