Mục lục
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