DEV Community

Artem Ptushkin
Artem Ptushkin

Posted on

Best practices on using Jackson and Lombok

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;
}
Enter fullscreen mode Exit fullscreen mode
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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
@Data
@SuperBuilder
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Cat extends Animal {
    private Integer lives;
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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

Discussion (3)

Collapse
bertilmuth profile image
Bertil Muth

If you want to get rid of the ConstructorProperties annotation as well, check out the Moonwlker library.

Collapse
art_ptushkin profile image
Artem Ptushkin Author

@bertilmuth that one is great actually, exactly the right follow up out of my thoughts on the topic.

Collapse
bertilmuth profile image
Bertil Muth

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.