Mục lục
Trong bài viết này chúng ta sẽ cùng nhau tìm cách xử lý Date trong Java khi serialize/deserialize sang Json với Jackson.
Maven dependency
Để sử dụng Jackson trong Project Maven chúng ta cần thêm dependency sau:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
Ở đây, mình sử dụng phiên bản 2.9.8, các bạn có thể sử dụng phiên bản mới hơn tại maven central.
Configuration
Trước tiên, chúng ta sẽ tạo Event class sử dụng xuyên suốt trong bài viết.
public class Event { private String name; private Date eventDate; public Event() { } public Event(String name, Date eventDate) { this.name = name; this.eventDate = eventDate; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getEventDate() { return eventDate; } public void setEventDate(Date eventDate) { this.eventDate = eventDate; } }
Serialize Date sang Timestamp
Phần đầu tiên sẽ hướng dẫn cách serialize java.util.Date với Jackson.
Ví dụ serialize Event object sang Json.
public class Main { public static void main(String[] agrs) throws IOException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm"); df.setTimeZone(TimeZone.getTimeZone("UTC")); Date date = df.parse("01-01-1970 01:00"); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(event); System.out.println(json); } }
Một điểm quan trọng chúng ta cần lưu ý rằng Jackson mặc định sẽ chuyển Date sang Timestamp (số millisecond tính từ 01 – 01 – 1970 UTC) khi serialize sang Json.
Như vậy, kết quả thực tế là:
{ "name":"party", "eventDate":3600000 }
Serialize Date theo chuẩn ISO – 8601
Như đã đề cập ở trên, mặc định Jackson sẽ chuyển Date sang định dạng timestamps khi serialize. Để serialize Date sang Json theo chuẩn IOS – 8601
public class Main { public static void main(String[] agrs) throws IOException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String toParse = "01-01-1970 02:30"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // StdDateFormat is ISO8601 since jackson 2.9 mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); String result = mapper.writeValueAsString(event); System.out.println(result); } }
Kết quả serialize là định dạng Date theo chuẩn ISO cho khả năng đọc hiểu Json dễ dàng hơn timestamps.
{ "name":"party", "eventDate":"1970-01-01T02:30:00.000+00:00" }
Configure ObjectMapper DateFormat
Giải pháp ở phần trước cho phép chúng ta định dạng theo chuẩn ISO, thế nhưng vẫn còn rất nhiều định dạng Date khác mà từng dự án riêng sẽ cần.
Trong phần này chúng ta sẽ cấu hình định dạng cho ObjectMapper cho phép chúng ta chỉ định bất kỳ định dạng Date nào có sẵn.
Ví dụ định dạng dd-MM-yyyy cho Date khi serialize sang Json.
SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy"); ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(df);
public class Main { public static void main(String[] agrs) throws IOException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy"); String toParse = "20-12-2014 02:30"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(df); String result = mapper.writeValueAsString(event); System.out.println(result); } }
Output:
{ "name":"party", "eventDate":"20-12-2014" }
Sử dụng @JsonFormat để định dạng Date
Với cách cấu hình trên thì tất cả các Object chứa Date field đều được định dạng theo cấu hình mà chúng ta đã cài đặt sẵn. Để kiểm soát định dạng Date cho từng class riêng biệt chúng ta có thể sử dụng @JsonFormat cho các Date field tương ứng.
public class Event { public String name; @JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") public Date eventDate; // ... }
Tiếp theo thử bỏ cấu hình định dạng Date cho ObjectMapper và kiểm thử.
public class Main { public static void main(String[] agrs) throws IOException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String toParse = "20-12-2014 02:30:00"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); System.out.println(result); } }
Output:
{ "name":"party", "eventDate":"19-12-2014" }
Cusom Date Serialize
Nếu có những yêu cầu đặc biệt mà các định dạng Date hiện hành đều không đáp ứng được, thì chúng ta có thể custom serializer với @JsonSerialize annotation.
Giả sử mình chỉ muốn xuất định đạng Date sang Json dd/MM.
public class CustomDateSerializer extends StdSerializer<Date> { private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateSerializer() { this(null); } public CustomDateSerializer(Class t) { super(t); } @Override public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException { gen.writeString(value.getDay() + "/" + value.getMonth()); } }
Chỉ định CustomDateSerializer cho eventDate field với @JsonSerializer annotation.
public class Event { public String name; @JsonSerialize(using = CustomDateSerializer.class) public Date eventDate; // ... }
Cuối cùng chúng ta sẽ kiểm thử kết quả:
public class Main { public static void main(String[] agrs) throws IOException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); String toParse = "20-12-2014 02:30:00"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); System.out.println(result); } }
Output:
{ "name":"party", "eventDate":"6/11" }
Serialize Java 8 Date với Jackson
Java 8 mở rộng với rất nhiều kiểu dữ liệu hỗ trợ thao tác với Date, trong phần này chúng ta sẽ sử dụng Module mà Jackson đã cung cấp để có thể tương thích với các dữ liệu Date trong Java 8.
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.9.7</version> </dependency>
Sau khi download thư viện thành công, chúng ta cần đăng ký JavaTimeModule với Jackson, phần còn lại sẽ do Jackson xử lý.
Ví dụ serialize LocalDateTime sang Json.
public class Main { public static void main(String[] agrs) throws IOException { LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String result = mapper.writeValueAsString(date); System.out.println(result); } }
Deserialize Date
Trong phần này, chúng ta sẽ tìm cách deserialize Date trong Json về lại Java Date object.
public class Main { public static void main(String[] agrs) throws IOException { String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014 02:30:00\"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(df); Event event = mapper.readerFor(Event.class).readValue(json); System.out.println(event.getName() + " - " + event.getEventDate()); } }
Output: party – Sat Dec 20 02:30:00 ICT 2014
Custom Deserializer
Nếu có các yêu cầu riêng biệt cần tinh chỉnh mới có thể chuyển Json sang Java object chúng ta có thể tạo ra một custom deserializer với @JsonDeserializer annotation.
Ví dụ chúng ta có chuỗi Json nhưng eventDate chỉ chứa ngày và tháng, khi deserialize chúng ta mong muốn mặc định năm sẽ là 2020.
Trước tiên chúng ta cần tạo CustomDateDeserializer class như là một custom deserializer
public class CustomDateDeserializer extends StdDeserializer<Date> { public CustomDateDeserializer() { this(null); } public CustomDateDeserializer(Class<?> vc) { super(vc); } @Override public Date deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException { String date = jsonparser.getText(); String[] dates = date.split("-"); return new Date(2020, Integer.parseInt(dates[1]), Integer.parseInt(dates[0])); } }
Tiếp theo chú thích cho eventDate sử dụng CustomDateDeserializer trong deserialization.
public class Event { public String name; @JsonDeserialize(using = CustomDateDeserializer.class) public Date eventDate; // ... }
Cuối cùng tiến hành kiểm thử
public class Main { public static void main(String[] agrs) throws IOException { String json = "{\"name\":\"party\",\"eventDate\":\"20-12\"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); ObjectMapper mapper = new ObjectMapper(); Event event = mapper.readerFor(Event.class).readValue(json); System.out.println(event.getName() + " - " + event.getEventDate()); } }
Output :party – Thu Jan 20 00:00:00 ICT 3921
Tóm lược
Định dạng Date trong serializer/deserializer Json là một một vấn đề đau đầu bởi có rất nhiều trường hợp chúng ta phải xử lý. Bài viết này đã cung cấp cho chúng ta nhiều cách tiếp cận khác nhau để các bạn có thể áp dụng trong các trường hợp cụ thể, giúp giải quyết nhanh chóng bài toán.
Nguồn tham khảo