Best practices on using Jackson and Lombok
Tags: java, codequality
Published: 2021-08-17 11:31:25

Jump to the examples [here](#implementation)!

### Issues

In modern Java stack [Lombok](https://projectlombok.org/) and [Jackson](https://github.com/FasterXML/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

  ```java
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;
}
  ```

  ```java
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:

  ```java
@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

```java
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Data
@SuperBuilder
@NoArgsConstructor
public class Animal {
	private Long id;
	private String description;
}
```

```java
@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

```java
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](https://github.com/artemptushkin/java-dto-playground) with examples