Gson cung cấp một tập hợp các Annotation (chú thích) để đơn giản hóa quá trình Serialization và Deserialisation. Trong bài này, tôi sẽ hướng dẫn các bạn sử dụng các chú thích này như thế nào và làm thế nào chúng có thể đơn giản hóa việc sử dụng Gson để chuyển đổi giữa các đối tượng Java và các đối tượng JSON.
Nội dung
Tùy chỉnh tên field sử dụng @SerializedName
Hãy xem ví dụ dưới đây:
package com.gpcoder.gson.object; import com.google.gson.annotations.SerializedName; public class Student { @SerializedName("id") private int studentId; @SerializedName("name") private String studentName; @SerializedName("clazz") private String clazzId; public Student() { super(); } public Student(int studentId, String studentName, String clazzId) { super(); this.studentId = studentId; this.studentName = studentName; this.clazzId = clazzId; } @Override public String toString() { return "Student [studentId=" + studentId + ", studentName=" + studentName + ", clazzId=" + clazzId + "]"; } }
Trong ví dụ trên, các trường của lớp Student được chú thích với chú thích @SerializedName. Tham số (value) của chú thích này là tên được sử dụng khi thực hiện Serialization và Deserialisation các java object. Ví dụ, trường studentId trong Java được đại diện là id trong JSON.
Gson hỗ trợ chú thích này mà không cần thêm bất kỳ cấu hình nào, hãy xem cách sử dụng như bên dưới:
package com.gpcoder.gson.annotation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.gpcoder.gson.object.Student; public class SerializedNameExample { public static void main(String[] args) { final GsonBuilder builder = new GsonBuilder(); final Gson gson = builder.create(); final Student student = new Student(1, "GP Coder", "Java Dev"); final String json = gson.toJson(student); System.out.println("Json: " + json); final Student student2 = gson.fromJson(json, Student.class); System.out.println("Java Object: " + student2); } }
Kết quả thực thi chương trình trên:
Json: {"id":1,"name":"GP Coder","clazz":"Java Dev"} Java Object: Student [studentId=1, studentName=GP Coder, clazzId=Java Dev]
Như bạn thấy giá trị của studentId trong java object tương ứng với key id trong chuỗi Json. Tương tự, studentName, clazzId tương ứng với key name và clazz trong chuỗi Json.
Loại trừ field sử dụng @Expose
Gson cung cấp hai loại chuyển đổi: Serialization (từ Java sang JSON) và Deserialisation (từ JSON sang Java). Các trường Java được đánh dấu transient (tạm thời) được loại trừ khỏi cả Serialization và Deserialisation . Do đó, thông tin không cần thiết có thể được đánh dấu là transient và Gson sẽ không được Serialization đến JSON.
public class User { private String name; private int age; private boolean transient password; // will not be serialized or deserialized }
Với Gson chúng ta có thể kiểm soát những gì được Serialization và Deserialisation độc lập bằng cách sử dụng chỉ chú thích (Annotation). Ngoài ra, chúng ta cũng có thể sử dụng một tùy chỉnh JsonDeserializer<T> và một tùy chỉnh JsonSerializer<T> để loại trừ các field (phần này sẽ được giới thiệu ở các bài viết kế tiếp). Mặc dù các giao diện này cung cấp khả năng kiểm soát và tính linh hoạt hoàn toàn nhưng cách tiếp cận chú thích được mô tả trong bài viết này đơn giản hơn vì nó không đòi hỏi các lớp bổ sung. Hãy xem ví dụ sau:
package com.gpcoder.gson.object; import com.google.gson.annotations.Expose; public class Account { @Expose(deserialize = false) private String accountNumber; @Expose private String iban; @Expose(serialize = false) private String owner; @Expose(serialize = false, deserialize = false) private String address; private String pin; public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public String getIban() { return iban; } public void setIban(String iban) { this.iban = iban; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getPin() { return pin; } public void setPin(String pin) { this.pin = pin; } }
Ở ví dụ trên, tôi sử dụng @Expose có hai thuộc tính tùy chọn: deserialize và serialize. Thông qua hai thuộc tính này chúng ta có thể kiểm soát những gì được Serialization và Deserialisation. Các giá trị mặc định cho hai thuộc tính đều là true.
Để có thể làm việc với chú thích này, chúng ta cần phải sử dụng cấu hình phù hợp. Khác với chú giải @SerializedName, chúng ta cần phải cấu hình Gson chỉ Serialization và Deserialisation các trường được chú thích và bỏ qua phần còn lại như thể hiện trong đoạn mã sau đây:
package com.gpcoder.gson.annotation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.gpcoder.gson.object.Account; public class ExposeExample { public static void main(final String[] args) throws Exception { final GsonBuilder builder = new GsonBuilder(); builder.excludeFieldsWithoutExposeAnnotation(); final Gson gson = builder.create(); final 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"); final String json = gson.toJson(account); System.out.println("Json: " + json); String json2 = "{\"accountNumber\":\"A123 4567 8909\",\"iban\":\"IBAN 11 22 33 44\",\"owner\":\"GP Coder\",\"pin\":\"123456\",\"address\":\"Can Tho, Viet Nam\"}"; final Account account2 = gson.fromJson(json2, Account.class); System.out.println("Java Object: "); System.out.printf(" + Account Number: %s%n", account2.getAccountNumber()); System.out.printf(" + IBAN: %s%n", account2.getIban()); System.out.printf(" + Owner: %s%n", account2.getOwner()); System.out.printf(" + Pin: %s%n", account2.getPin()); System.out.printf(" + Address: %s%n", account2.getAddress()); } }
Kết quả thực thi chương trình trên:
Json: {"accountNumber":"A123 4567 8909","iban":"IB 11 22 33 44"} Java Object: + Account Number: null + IBAN: IBAN 11 22 33 44 + Owner: GP Coder + Pin: null + Address: null
Như bạn thấy, Khi thực hiện Serialization chỉ accountNumber và iban được chuyển đổi từ Java Object sang chuỗi Json. Khi thực hiện Deserialisation chỉ owner và iban được chuyển đổi từ Json sang Java Object.
Quản lý phiên bản với @Since và @Until
Gson cung cấp hai chú thích được gọi là @Since và @Until có thể được sử dụng để kiểm soát các field khi chuyển đổi giữa các đối tượng Java và các đối tượng JSON.
Hai chú thích này hữu ích khi bạn muốn quản lý phiên bản của các lớp Json của mình. Bạn có thể kiểm soát nếu một field cụ thể thể được xem xét để serialization / deserialization dựa trên phiên bản nhất định.
- Chú thích @Since : cho biết số phiên bản mà một field đã được thêm vào. Nghĩa là field này sẽ chỉ được xem xét cho Serialization / deserialization bắt đầu từ phiên bản nhất định. Trước phiên bản đó, nó sẽ bị bỏ qua.
- Chú thích @Until : cho biết số phiên bản mà một field đã bị loại bỏ. Nghĩa là field này sẽ chỉ được xem xét cho Serialization / deserialization cho đến khi phiên bản nhất định. Sau phiên bản đó, nó sẽ bị bỏ qua.
Những chú thích này được áp dụng trên cấp field và chỉ có hiệu lực khi Gson được tạo bằng GsonBuilder và gọi phương thức GsonBuilder.setVersion() như dưới đây:
Gson gson = new GsonBuilder().setVersion(double).create();
Ví dụ:
package com.gpcoder.gson.object; import com.google.gson.annotations.Since; import com.google.gson.annotations.Until; public class MemberInfo { private int id; private String name; @Until(1.7) private String yahooAccount; @Since(2.3) private String facebookAccount; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getYahooAccount() { return yahooAccount; } public void setYahooAccount(String yahooAccount) { this.yahooAccount = yahooAccount; } public String getFacebookAccount() { return facebookAccount; } public void setFacebookAccount(String facebookAccount) { this.facebookAccount = facebookAccount; } }
Trong lớp trên, một lớp MemberInfo có 4 field: id, name, yahooAccount và facebookAccount. Trong đó, field yahooAccount được xóa bỏ từ phiên bản 1.7 và field facebookAccount được thêm từ phiên bản 2.3.
Tương tự như @Expose, để chú thích này có hiệu lực chúng ta phải thực hiện các cấu hình như sau:
package com.gpcoder.gson.annotation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.gpcoder.gson.object.MemberInfo; public class VersioningExample { public static void main(final String[] args) throws Exception { final GsonBuilder builder = new GsonBuilder(); builder.setVersion(2.0); final Gson gson = builder.create(); final MemberInfo member = new MemberInfo(); member.setId(1); member.setName("GP Coder"); member.setYahooAccount("gpcoder@yahoo.com"); // Until version 1.7 member.setFacebookAccount("facebook.com/GPCoder/"); // Since version 2.0 final String json = gson.toJson(member); System.out.println("Json: " + json); // Parse JSON to Java String json2 = "{\"id\":1,\"name\":\"GP Coder\",\"yahooAccount\":\"gpcoder@yahoo.com\",\"facebookAccount\":\"facebook.com/GPCoder/\"}"; final MemberInfo otherMember = gson.fromJson(json2, MemberInfo.class); System.out.println("Deserialised (version 1.0)"); System.out.printf(" + Id: %s%n", otherMember.getId()); System.out.printf(" + Name: %s%n", otherMember.getName()); System.out.printf(" + YahooAccount: %s Until(1.7)%n", otherMember.getYahooAccount()); System.out.printf(" + FacebookAccount: %s Since(2.3)%n", otherMember.getFacebookAccount()); } }
Kết quả thực thi chương trình trên:
Json: {"id":1,"name":"GP Coder"} Deserialised (version 1.0) + Id: 1 + Name: GP Coder + YahooAccount: null Until(1.7) + FacebookAccount: null Since(2.3)
Trong ví dụ này, tôi đã xác định phiên bản là 2.0, có nghĩa là chỉ có Id và Name sẽ được xử lý. FacebookAccount được thêm vào phiên bản 2.3, do đó không thõa điều kiện và YahooAccount đã bị xóa sau phiên bản 1.7 nên cũng không thõa điều kiện.
Áp dụng tùy chỉnh TypeAdapter, JsonSerializer, JsonDeserializer với @JsonAdapter
Chú thích này có thể được sử dụng field hoặc class để chỉ định lớp tùy chỉnh Gson TypeAdapter, JsonSerializer, JsonDeserializer được sử dụng trong khi serialization/ deserialization. Các lớp này sẽ được giới thiệu ở các bài viết tiếp theo.
Dưới đây là một adapter tùy chỉnh mở rộng từ lớp TypeAdapter và ghi đè lên các phương thức read() và write(). Các phương thức này là được sử dụng trong quá trình serialization/ deserialization của lớp / trường được cấu hình để sử dụng bộ điều hợp Loại tùy chỉnh này.
Các phương thức read() và write() này dựa trên cách tiếp cận dựa trên token. Trong quá trình deserialization / read, chúng ta đọc từng token riêng lẻ sử dụng phương thức hasNext và nextName của JsonReader, kiểm tra kiểu của chúng và nhận giá trị dựa trên kiểu sử dụng các phương thức getXXX. Tương tự, trong quá trình Serialization / write, chúng ta sử dụng các phương thức giá trị name và value của JsonWriter. Cần lưu cần xác định chính xác các phương thức beginObject/ endObject và beginArray/ endArray tương ứng với chuỗi json Object và Array.
Hãy xem ví dụ sau:
package com.gpcoder.gson.object; import java.util.Arrays; import java.util.Date; import com.google.gson.annotations.JsonAdapter; import com.gpcoder.gson.annotation.CustomTypeAdapter; public class AmazonBook { private String title; private String[] authors; private String isbn10; private String isbn13; private Double price; private Date publishedDate; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String[] getAuthors() { return authors; } public void setAuthors(String[] authors) { this.authors = authors; } public String getIsbn10() { return isbn10; } public void setIsbn10(String isbn10) { this.isbn10 = isbn10; } public String getIsbn13() { return isbn13; } public void setIsbn13(String isbn13) { this.isbn13 = isbn13; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public Date getPublishedDate() { return publishedDate; } public void setPublishedDate(Date publishedDate) { this.publishedDate = publishedDate; } @Override public String toString() { return "Book [title=" + title + ", authors=" + Arrays.toString(authors) + ", isbn10=" + isbn10 + ", isbn13=" + isbn13 + ", price=" + price + ", publishedDate=" + publishedDate + "]"; } }
Tạo một lớp custom TypeAdapter như sau:
package com.gpcoder.gson.annotation; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.gpcoder.gson.object.AmazonBook; public class CustomTypeAdapter extends TypeAdapter<AmazonBook> { public static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); @Override public AmazonBook read(final JsonReader in) throws IOException { final AmazonBook book = new AmazonBook(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "title": book.setTitle(in.nextString()); break; case "isbn-10": book.setIsbn10(in.nextString()); break; case "isbn-13": book.setIsbn13(in.nextString()); break; case "price": book.setPrice(in.nextDouble()); break; case "publishedDate": Date publishedDate = null; try { publishedDate = sdf.parse(in.nextString()); } catch (ParseException e) { e.printStackTrace(); } book.setPublishedDate(publishedDate); break; case "authors": in.beginArray(); final List<String> authors = new ArrayList<>(); while (in.hasNext()) { authors.add(in.nextString()); } in.endArray(); book.setAuthors(authors.toArray(new String[authors.size()])); break; } } in.endObject(); return book; } @Override public void write(final JsonWriter out, final AmazonBook book) throws IOException { out.beginObject(); out.name("title").value(book.getTitle()); out.name("isbn-10").value(book.getIsbn10()); out.name("isbn-13").value(book.getIsbn13()); out.name("price").value(book.getPrice()); out.name("publishedDate").value(sdf.format(book.getPublishedDate())); out.name("authors"); out.beginArray(); for (final String author : book.getAuthors()) { out.value(author); } out.endArray(); out.endObject(); } }
Bây giờ hãy thêm @JsonAdapter vào lớp AmazonBook để xác định sử dụng lớp custom Adapter trên:
@JsonAdapter(value = CustomTypeAdapter.class) public class AmazonBook { // Fields ... }
Chương trình sử dụng JsonAdaper:
package com.gpcoder.gson.annotation; import java.util.Calendar; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.gpcoder.gson.object.AmazonBook; public class JsonAdapterExample { public static void main(String args[]) { final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); final Gson gson = gsonBuilder.create(); final AmazonBook book = new AmazonBook(); book.setTitle("Head First Design Patterns"); book.setIsbn10("0596007124"); book.setIsbn13("978-0596007126"); book.setPrice(52.41); Calendar c = Calendar.getInstance(); c.set(2004, Calendar.OCTOBER, 1); book.setPublishedDate(c.getTime()); String[] authors = new String[] { "Eric Freeman", "Bert Bates", "Kathy Sierra", "Elisabeth Robson" }; book.setAuthors(authors); System.out.println("Convert Book object to JSON string: "); final String json = gson.toJson(book); System.out.println(json); System.out.println("Convert JSON String to Book object: "); final AmazonBook parsedBook1 = gson.fromJson(json, AmazonBook.class); System.out.println(parsedBook1); } }
Kết quả thực thi chương trình trên:
Convert Book object to JSON string: { "title": "Head First Design Patterns", "isbn-10": "0596007124", "isbn-13": "978-0596007126", "price": 52.41, "publishedDate": "01/10/2004", "authors": [ "Eric Freeman", "Bert Bates", "Kathy Sierra", "Elisabeth Robson" ] } Convert JSON String to Book object: Book [title=Head First Design Patterns, authors=[Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson], isbn10=0596007124, isbn13=978-0596007126, price=52.41, publishedDate=Fri Oct 01 00:00:00 ICT 2004]
Ngoài ra, bạn có thể chọn không sử dụng chú thích này @JsonAdapter và chỉ cần đăng ký custom TypeAdapter ở trên với GsonBuilder như dưới đây:
final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(AmazonBook.class, new CustomTypeAdapter()); gsonBuilder.setPrettyPrinting();
Kết quả cũng tương tự như việc sử dụng Annotation @JsonAdapter. Chi tiết về custom TypeAdapter, hãy xem bài viết Hướng dẫn sử dụng Gson TypeAdapter.
Trên đây là những ví dụ cơ bản về sử dụng các Annotation (chú thích) của Gson. Các chú thích này cung cấp rất nhiều tùy chỉnh mà không cần phải viết các lớp phức tạp. Tuy nhiên, các chú thích có những hạn chế và không thể xử lý tất cả các trường hợp như chuyển đổi giữa các cấu trúc khác nhau hoặc cần xử lý cache để tăng hiệu suất. Khi trường hợp cụ thể phát sinh, chúng ta nên sử dụng tùy chỉnh JsonDeserializer<T> và một JsonSerializer<T>. Phần này sẽ được giới thiệu ở bài viết tiếp theo.