Cách customize Jackson ObjectMapper trong Spring Boot

Trong các project Spring Boot, mặc định nó sẽ sử dụng Jackson với một ObjectMapper instance được cấu hình tự động để serialize các response trả về cho client và deserialize JSON từ client gửi lên. 

Nếu các bạn chưa có kiến thức về Jackson thì có thể tham khảo qua các bài hướng dẫn về Jackson được tổng hợp tại đây.

Default Configuration

Mặc định, Spring Boot sẽ cấu hình tự động và nó sẽ vô hiệu hóa các tính năng sau:

  • Disable MapperFeature.DEFAULT_VIEW_INCLUSION
  • Disable DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
  • Disable SerializationFeature.WRITE_DATES_AS_TIMESTAMP

Giả sử chúng ta có một model dùng để giao tiếp giữa client và server.

@Builder
@Getter
public class Coffee {
    private String name;
    private String brand;
    private LocalDateTime date;
}

Tiếp theo chúng ta có một REST API đơn giản nhận JSON object tử client và gửi trả lại.

@RestController
@RequestMapping("/")
public class CoffeeController {

    @GetMapping("/coffee")
    public Coffee getCoffee(
            @RequestParam(required = false) String brand,
            @RequestParam(required = false) String name) {

        return Coffee.builder()
                .brand(brand)
                .name(name)
                .date(LocalDateTime.now())
                .build();
    }
}

Mặc định khi chúng ta request đến server /coffee?brand=Lavazza thì kết quả sẽ như sau:

curl http://localhost:8080/coffee?brand=Lavazza
----
{"name":null,"brand":"Lavazza","date":"2020-12-31T10:00:43.1256138"}

Nếu chúng ta muốn loại bỏ các giá trị NULL khỏi kết quả về trả về cũng như điều chỉnh lại định dạng ngày tháng (dd-MM-yyyy HH:mm). Kết quả trả về sẽ trông như thế này:

{
  "brand": "Lavazza", "date": "04-11-2020 10:34"
}

Để đạt được kết quả trên, chúng ta cần triển khai một số tùy biến với ObjectMapper hoặc có thể override nó bằng những cách sau đây.

Tùy biến ObjectMapper

Trong phần này chúng ta sẽ xem cách tùy biến ObjectMapper trong Spring Boot

Application Properties và Custom Jackson Module

Cách đơn giản nhất để cấu hình ObjectMapper là thông qua các file cấu hình application propertites của ứng dụng theo cú pháp sau:

spring.jackson.<category_name>.<feature_name>=true,false

Ví dụ, nếu chúng ta muốn vô hiệu hóa SerializationFeature.WRITE_DATES_AS_TIMESTAMPS thì chúng ta sẽ thêm vào file cấu hình

spring.jackson.serialization.write-dates-as-timestamps=false

Ngoài ra chúng ta có thể cấu hình cách mà thuộc tính được thêm vào kết quả trả về

spring.jackson.default-property-inclusion=always, non_null, non_absent, non_default, non_empty

Gỉa sử với nhu cầu loại bỏ các thuộc tính có giá trị NULL ra khởi JSON trả về thì chúng ta có thể cấu hình

spring.jackson.default-property-inclusion=non_null

Và kết quả sẽ như sau

curl http://localhost:8080/coffee?brand=Lavazza
{"brand":"Lavazza","date":"2020-12-31T10:20:32.696422"}

Với cách cấu hình đơn giản trên thì chúng ta vẫn chưu thể tùy biến định dạng Date trong response. Do vậy chúng ta cần tạo một JavaTimeModule mới dùng để tùy biến định dạng như ý muốn

@Configuration
public class AppConfig {

    public static final String DATETIME_FORMAT = "dd-MM-yyyy HH:mm";
    public static LocalDateTimeSerializer LOCAL_DATETIME_SERIALIZER =
            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT));

    @Bean
    public Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(LOCAL_DATETIME_SERIALIZER);
        return module;
    }
}

Và kết quả sẽ như sau

curl http://localhost:8080/coffee?brand=Lavazza
{"brand":"Lavazza","date":"31-12-2020 10:29"}

Jackson2ObjectMapperBuilderCustomizer

Có một cách đơn giản và ngắn gọn hơn là chúng ta có thể cấu hình thông qua Jackson2ObjectMapperBuilderCustomizer. Nó cho phép chúng ta định nghĩa các cấu hình mặc định sẽ được áp dụng cho default ObjectMapper được tạo ra từ Jackson2ObjectMapperBuilder.

@Configuration
public class AppConfig {

    public static final String DATETIME_FORMAT = "dd-MM-yyyy HH:mm";
    public static LocalDateTimeSerializer LOCAL_DATETIME_SERIALIZER =
            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT));
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
        return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL)
                .serializers(LOCAL_DATETIME_SERIALIZER);
    }
}

Overriding the Default Configuration

Nếu không muốn sử dụng ObjectMapper mặc định mà Spring Boot đã cấu hình sẵn cho chúng ta thì vẫn có một số cách sau cho phép chúng ta toàn quyền định nghĩa các cấu hình cho ObjectMapper.

ObjectMapper

Cách đơn giản nhất để ghi đè cấu hình mặc định là xác định một bean ObjectMapper và đánh dấu nó là @Primary:

@Bean
@Primary
public ObjectMapper objectMapper() {
    JavaTimeModule module = new JavaTimeModule();
    module.addSerializer(LOCAL_DATETIME_SERIALIZER);
    return new ObjectMapper()
      .setSerializationInclusion(JsonInclude.Include.NON_NULL)
      .registerModule(module);
}

Chúng ta nên sử dụng cách tiếp cận này khi chúng ta muốn có toàn quyền kiểm soát quá trình tuần tự hóa và chúng ta không muốn cho phép cấu hình bên ngoài.

Jackson2ObjectMapperBuilder

Một cách tiếp cận  khác là xác định một bean Jackson2ObjectMapperBuilder. Trên thực tế, Spring Boot đang sử dụng class này để khởi tạo ObjectMapper tự động.

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
}

Ở trên chúng ta cấu hình 2 tùy chọn mặc định:

  • Vô hiệu hóa MapperFeature.DEFAULT_VIEW_INCLUSION
  • Vô hiệu hóa DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

Theo tài liệu Jackson2ObjectMapperBuilder, nó cũng sẽ đăng ký một số module nếu chúng có mặt trên classpath:

  • jackson-datatype-jdk8: support for other Java 8 types like Optional
  • jackson-datatype-jsr310: support for Java 8 Date and Time API types
  • jackson-datatype-joda: support for Joda-Time types
  • jackson-module-kotlin: support for Kotlin classes and data classes

MappingJackson2HttpMessageConverter

Chúng ta chỉ có thể định nghĩa một bean với kiểu MappingJackson2HttpMessageConverter và Spring Boot sẽ tự động sử dụng nó:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
    return new MappingJackson2HttpMessageConverter(builder.build());
}

Kết bài

Qua biết trên, chúng ta đã biết cách custom ObjectMapper trong Spring Boot, vì thông thường chúng ta thường sử dụng mặc định, tuy nhiên trong một số trường hợp bắt buộc chúng ta phải cấu hình thì bài viết này sẽ rất hữu ích. Tuy nhiên bài viết trên chỉ nêu ra 2 vấn đề để tùy biến, ngoài ra có rất nhiều các custom khác trong Jackson mà các bạn có thể tham khảo và thực hiện một số custom khác phù hợp với nhu cầu.

Mã nguồn được mình công khai trên gitlab để các bạn có thể tham khảo: objectmapper

Nguồn tham khảo

https://www.baeldung.com/spring-boot-customize-jackson-objectmapper

 

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x