Trong bài trước, tôi đã hướng dẫn các bạn sử dụng thư viện Jackson để chuyển đổi từ đối tượng Java sang JSON và từ JSON sang đối tượng Java. Trong bài này, tôi sẽ hướng dẫn bạn sử dụng các Annotation (chú thích) có sẵn của thư viện Jackson để tùy chỉnh quá trình chuyển đổi giữa các đối tượng Java và các đối tượng JSON.
Ví dụ sử dụng Jackson Annotations
Account.java
package com.gpcoder.jackson.object; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.gpcoder.jackson.annotation.CustomDateDeserializer; import com.gpcoder.jackson.annotation.CustomDateSerializer; @JsonIgnoreProperties({ "iban", "pin" }) public class Account { @JsonProperty("number") private String accountNumber; private String iban; private String pin; @JsonIgnore private String owner; @JsonProperty private String address; @JsonProperty @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy") private Date createdDate; @JsonSerialize(using = CustomDateSerializer.class) @JsonDeserialize(using = CustomDateDeserializer.class) private Date expiredDate; private String notAnnotation; private String notAnnotationWithSetter; @JsonProperty private List<String> services = new ArrayList<String>(); private Map<String, Object> history = new HashMap<String, Object>(); @JsonAnyGetter public Map<String, Object> any() { return history; } @JsonAnySetter public void set(String name, Object value) { history.put(name, value); } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public void setIban(String iban) { this.iban = iban; } public void setPin(String pin) { this.pin = pin; } public void setOwner(String owner) { this.owner = owner; } public void setAddress(String address) { this.address = address; } public void setServices(List<String> services) { this.services = services; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } public void setExpiredDate(Date expiredDate) { this.expiredDate = expiredDate; } public void setHistory(Map<String, Object> history) { this.history = history; } public String getNotAnnotationWithSetter() { return notAnnotationWithSetter; } @Override public String toString() { return "Account [accountNumber=" + accountNumber + ", iban=" + iban + ", pin=" + pin + ", owner=" + owner + ", address=" + address + ", createdDate=" + createdDate + ", expiredDate=" + expiredDate + ", notAnnotation=" + notAnnotation + ", notAnnotationWithSetter=" + notAnnotationWithSetter + ", services=" + services + ", history=" + history + "]"; } }
CustomDateSerializer.java
package com.gpcoder.jackson.annotation; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class CustomDateSerializer extends JsonSerializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); @Override public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException { String formattedDate = dateFormat.format(date); generator.writeString(formattedDate); } }
CustomDateDeserializer.java
package com.gpcoder.jackson.annotation; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; public class CustomDateDeserializer extends JsonDeserializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); @Override public Date deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException { final String dateString = jsonparser.getText(); try { return dateFormat.parse(dateString); } catch (Exception ex) { ex.printStackTrace(); } return null; } }
JacksonAnnotationExample.java
package com.gpcoder.jackson.annotation; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.gpcoder.jackson.object.Account; public class JacksonAnnotationExample { public static final String JSON_FILE = "data/result.json"; public static void main(String args[]) throws JsonParseException, JsonMappingException, IOException { Account account = getAccount(); // Convert Object to JSON string ObjectMapper mapper = new ObjectMapper(); mapper.writerWithDefaultPrettyPrinter().writeValue(new File(JSON_FILE), account); // Convert a JSON to a Object String jsonString = "{" + "\"address\" : \"Can Tho, Viet Nam\"," + "\"createdDate\" : \"28/01/2018\"," + "\"expiredDate\" : \"28/01/2018\"," + "\"notAnnotationWithSetter\" : null," + "\"services\" : [ \"Internet Banking\", \"Mobile Banking\" ]," + "\"number\" : \"A123 4567 8909\"," + "\"20180128\" : \"Change password\"," + "\"20180101\" : \"Withdraw 100$\"," + "\"add_1\" : \"Add unknown key 1\"," + "\"add_2\" : \"Add unknown key 2\"" + "}"; // Account account2 = mapper.readValue(new File(JSON_FILE), Account.class); Account account2 = mapper.readValue(jsonString, Account.class); System.out.println(account2); } private static Account getAccount() { Account account = new Account(); account.setAccountNumber("A123 4567 8909"); account.setIban("IB 11 22 33 44"); account.setOwner("GP Coder"); account.setPin("123456"); account.setAddress("Can Tho, Viet Nam"); account.setCreatedDate(Calendar.getInstance().getTime()); account.setExpiredDate(Calendar.getInstance().getTime()); String[] services = { "Internet Banking", "Mobile Banking" }; account.setServices(Arrays.asList(services)); Map<String, Object> history = new HashMap<>(); history.put("20180101", "Withdraw 100$"); history.put("20180128", "Change password"); account.setHistory(history); return account; } }
Kết quả thực thi chương trình trên: result.json được tạo ra trong thư mục data. Nội dung của file result.json như sau:
[json]
{
“address” : “Can Tho, Viet Nam”,
“createdDate” : “28/01/2018”,
“expiredDate” : “28/01/2018”,
“notAnnotationWithSetter” : null,
“services” : [ “Internet Banking”, “Mobile Banking” ],
“number” : “A123 4567 8909”,
“20180128” : “Change password”,
“20180101” : “Withdraw 100$”
}
[/json]
Nội dung của console như sau:
Account [accountNumber=A123 4567 8909, iban=null, pin=null, owner=null, address=Can Tho, Viet Nam, createdDate=Sun Jan 28 07:00:00 ICT 2018, expiredDate=Sun Jan 28 00:00:00 ICT 2018, notAnnotation=null, notAnnotationWithSetter=null, services=[Internet Banking, Mobile Banking], history={add_2=Add unknown key 2, 20180128=Change password, add_1=Add unknown key 1, 20180101=Withdraw 100$}]
Các Annotation được sử dụng trong ví dụ
@JsonProperty
Chú thích này có thể được sử dụng trên một thuộc tính (field) hoặc phương thức (method) sẽ được sử dụng cho việc Serialization and Deserialization dữ liệu JSON. Nó có một tham số tùy chọn là tên khóa, tùy chọn này hữu ích trong trường hợp tên field khác với tên của khóa (key) trong JSON.
Khi nào sử dụng:
– Nếu bạn chỉ muốn khai báo thuộc tính và không phải là getter / setters.
– Nếu bạn đang sử dụng thuộc tính và getter / setters nhưng muốn sử dụng một tên getter / setter hoặc property khác với tên của key trong JSON. Chỉ cần đặt tham số tên trong các Annotation với tên key thực tế trong JSON.
Ví dụ ở trên tôi sử dụng thuộc tính @JsonProperty cho các field: accountNumber, address, createdDate, expiredDate, services. Các field này sẽ được Serialize sang chuỗi JSON.
Theo mặc định, Jackson sẽ lấy tên của field làm key của chuỗi JSON. Tuy nhiên chúng ta có thể thay đổi tên key nằm bằng cách sử dụng như field accountNumber trong ví dụ trên.
Trong ví dụ trên, các bạn cũng thấy là 2 field notAnnotation và notAnnotationWithSetter không được đánh dấu Annotation @JsonProperty nhưng field notAnnotationWithSetter được Serialize vì Jackson mặc định kiểm tra nếu field có @JsonProperty hoặc Getters sẽ được Serialize. Trong trường hợp này, field notAnnotation không có Annotation @JsonProperty cũng không có Getters nên không được Serialize, field notAnnotationWithSetter không có Annotation @JsonProperty nhưng có Getters nên được Serialize.
@JsonIgnoreProperties/ @JsonIgnore
@JsonIgnoreProperties: Chú thích cấp lớp (class) này có thể được sử dụng để loại trừ các thuộc tính nhất định trong quá trình Serialization and Deserialization dữ liệu JSON. Nghĩa là chúng sẽ không được ánh xạ tới nội dung JSON.
@JsonIgnore: chú thích cấp thuộc tính (field) này cũng được sử dụng để lại trừ field trong quá trình Serialization and Deserialization dữ liệu JSON.
Khi nào sử dụng:
– Nếu bạn muốn bỏ qua serialization / deserialization của một vài field nhất định.
Ví dụ ở trên tôi sử dụng @JsonIgnoreProperties({ “iban”, “pin” }) ở mức class hoặc sử dụng @JsonIgnore owner để loại trừ các field này khỏi quá trình Serialize chuỗi JSON.
@JsonAnySetter, @JsonAnyGetter
Những chú thích này hoạt động như một Catch-All và được áp dụng trên Getters / Setter làm việc với một Map. Nếu có bất kỳ giá trị JSON nào không được ánh xạ tới một thuộc tính trong Object, thì giá trị đó có thể bị bắt bởi @JsonAnySetter và được lưu trữ (deserialized) vào Map. Tương tự, các giá trị được lưu trữ trong Map có thể được tuần tự (serialized) trở lại JSON bằng @JsonAnyGetter.
Khi nào sử dụng:
– Nếu bạn không muốn khai báo một thuộc tính hoặc phương thức cho mọi khóa (key) có thể có trong JSON, nhưng vẫn muốn serialize/ deserialize dữ liệu.
Ví dụ ở trên, các JSON key: add_1, add_2, 20180128, 20180101 không có trong field của Object. Nhưng bằng cách sử dụng @JsonAnySetter & @JsonAnyGetter, chúng ta có thể serialize và deserialize các đối tượng này thông qua Map.
@JsonSerialize/ @JsonDeserialize
Hai chú thích này có thể được sử dụng để tùy chỉnh quá trình serialization/ deserialization mặc định (Java to JSON).
Khi nào sử dụng:
– Khi serialization/ deserialization mặc định không phù hợp với nhu cầu của bạn và bạn muốn có tùy chỉnh được áp dụng.
Ví dụ ở trên, tôi muốn expiredDate hiển thị định dạng dd/MM/yyyy. Nếu sử dụng Serialize thì không có được định dạng như mong muốn. Do đó, tôi đã viết lớp CustomDateSerializer để tùy chỉnh định dạng kiểu Date sang chuỗi dd/MM/yyyy của Json và lớp CustomDateDeserializer để tùy việc đọc chuỗi ngày dd/MM/yyyy sang kiểu Date của Java. Để 2 lớp này có hiệu lực, chúng ta cần đánh dấu @JsonSerialize(using = CustomDateSerializer.class) và @JsonDeserialize(using = CustomDateDeserializer.class) trên field expiredDate.
@JsonFormat
Đối với kiểu Date, ngoài việc sử dụng custom serialization/ deserialization như trên, Jackson có cung cấp một Annotation @JsonFormat để tùy chỉnh format dữ liệu kiểu Date/Time.
Khi nào sử dụng:
– Khi serialization/ deserialization dữ liệu mặc định không phù hợp và cần tùy chỉnh hiển thị nội dung kiểu Date/Time.
Ví dụ ở trên, field createdDate được đánh dấu @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “dd/MM/yyyy”). Kết quả cũng tương tự như việc thực hiện tạo 2 lớp custom JsonSerializer và JsonDeserializer.
Một vài Annotation khác
@JsonGetter
Chú thích @JsonGetter là một thay thế cho chú thích @JsonProperty để đánh dấu ở phương thức (method) được chỉ định như một phương thức getter.
public class Student { private String name; @JsonProperty("name") public String getName() { return name; } } // Một cách viết khác public class Student { private String name; @JsonGetter("name") public String getName() { return name; } }
@JsonSetter
@JsonSetter là một sự thay thế cho @JsonProperty được sử dụng để đánh dấu phương thức (method) như một phương thức setter.
public class Student { private String name; @JsonProperty("name") public String getName() { return name; } } // Một cách viết khác public class Student { private String name; @JsonSetter("name") public void setName() { this.name = name; } }
@JsonPropertyOrder
Chú giải @JsonPropertyOrder được sử dụng để xác định thứ tự của thuộc tính được Serialize sang JSON.
@JsonPropertyOrder({ "name", "id" }) public class JsonPropertyOrderExample { public int id; public String name; }
Kết quả hiển thị:
[json]
{
“name”:”GP Coder”,
“id”:1
}
[/json]
@JsonRootName
Chú thích @JsonRootName được sử dụng để chỉ định tên của wrapper gốc sẽ được sử dụng.
package com.gpcoder.jackson.annotation; import com.fasterxml.jackson.annotation.JsonRootName; @JsonRootName(value = "root") public class JsonRootNameExample { public int id; public String name; }
Kết quả hiển thị:
[json]
{
“root”: {
“name”:”GP Coder”,
“id”:1
}
}
[/json]
Trên đây là một vài các Annotation thường sử dụng. Ngoài ra còn nhiều Annotaion khác, các bạn hãy tham khảo thêm trên JavaDocs của thư viện Jackson.
Tài liệu tham khảo: