1. Giới thiệu về Gson
Chúng ta đã làm quen với Jackson và thấy vì sao nó được xem là tiêu chuẩn de facto để làm việc với JSON trong Java. Nhưng còn có một thư viện khác rất phổ biến, đặc biệt trong thế giới Android — đó là Gson. Gson được Google tạo ra như một giải pháp nhẹ và đơn giản để tuần tự hóa và giải tuần tự các đối tượng Java sang JSON. Nó được đánh giá cao nhờ rào cản gia nhập thấp: để bắt đầu sử dụng, hầu như không cần cấu hình — đa số tác vụ “chạy ngay” từ đầu.
Một ưu điểm khác của Gson là sự gọn nhẹ. Thư viện chiếm ít dung lượng và không kéo theo nhiều phụ thuộc, vì vậy nó thường được dùng ở nơi kích thước ứng dụng là yếu tố then chốt, ví dụ trên thiết bị di động. Gson đã trở thành chuẩn thực tế cho các dự án Android — độ nhỏ gọn và đơn giản ở đó đóng vai trò quyết định.
Nhân tiện, tên Gson là viết tắt của Google JSON. Đôi khi trong cộng đồng có cách giải thích vui — “Genius’ Son” (“con trai của thiên tài”), nhưng dĩ nhiên là không chính thức. Chỉ là chơi chữ thôi.
Tích hợp Gson vào dự án
Nếu bạn dùng Maven hoặc Gradle, chỉ cần thêm dependency (phiên bản có thể khác):
Maven:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
Gradle:
implementation 'com.google.code.gson:gson:2.10.1'
Hiện chúng ta chưa học cách build dự án, nên trước mắt bạn có thể tải tệp jar từ trang chính thức của Gson và thêm nó vào dự án.
2. Thao tác cơ bản: tuần tự hóa và giải tuần tự
Hãy xem cách tuần tự hóa và giải tuần tự đối tượng bằng Gson với ví dụ một lớp đơn giản.
Ví dụ: lớp User
// Lớp dùng cho ví dụ
public class User {
private String name;
private int age;
private boolean active;
// Hàm khởi tạo
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// Getter và setter (Gson dùng khi cần)
public String getName() { return name; }
public int getAge() { return age; }
public boolean isActive() { return active; }
}
Tuần tự hóa: đối tượng → JSON
import com.google.gson.Gson;
public class GsonExample {
public static void main(String[] args) {
User user = new User("Alice", 25, true);
Gson gson = new Gson();
String json = gson.toJson(user);
System.out.println(json);
// {"name":"Alice","age":25,"active":true}
}
}
Lưu ý: các trường được tuần tự hóa theo đúng tên trong lớp!
Giải tuần tự: JSON → đối tượng
public class GsonExample {
public static void main(String[] args) {
String json = "{\"name\":\"Bob\",\"age\":30,\"active\":false}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
System.out.println(user.getName()); // Bob
System.out.println(user.getAge()); // 30
System.out.println(user.isActive());// false
}
}
Làm việc với danh sách đối tượng
Gson làm việc với collection phức tạp hơn một chút so với Jackson, nhưng vẫn xử lý tốt.
import java.util.List;
import java.util.Arrays;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
public class GsonListExample {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", 25, true),
new User("Bob", 30, false)
);
Gson gson = new Gson();
String json = gson.toJson(users);
System.out.println(json);
// [{"name":"Alice","age":25,"active":true},{"name":"Bob","age":30,"active":false}]
// Giải tuần tự danh sách
Type userListType = new TypeToken<List<User>>(){}.getType();
List<User> users2 = gson.fromJson(json, userListType);
System.out.println(users2.get(0).getName()); // Alice
}
}
Lưu ý quan trọng: để giải tuần tự collection, hãy dùng TypeToken<>!
3. Cấu hình Gson: GsonBuilder
Gson cung cấp cấu hình linh hoạt thông qua lớp GsonBuilder. Với nó, bạn có thể bật pretty printing, tuần tự hóa null, định dạng ngày giờ và nhiều thứ khác.
Ví dụ: pretty printing và tuần tự hóa null
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonBuilderExample {
public static void main(String[] args) {
User user = new User("Charlie", 0, false);
Gson gson = new GsonBuilder()
.setPrettyPrinting() // In đẹp (có thụt dòng)
.serializeNulls() // Tuần tự hóa trường null
.create();
String json = gson.toJson(user);
System.out.println(json);
/*
{
"name": "Charlie",
"age": 0,
"active": false
}
*/
}
}
Định dạng ngày giờ
Nếu bạn có các trường kiểu Date, mặc định Gson sẽ tuần tự hóa chúng theo một định dạng riêng. Bạn có thể đặt định dạng của mình:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Date;
public class DateExample {
private String event;
private Date date;
public DateExample(String event, Date date) {
this.event = event;
this.date = date;
}
}
public class Main {
public static void main(String[] args) {
DateExample meeting = new DateExample("Team Meeting", new Date());
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
String json = gson.toJson(meeting);
System.out.println(json);
// {"event":"Team Meeting","date":"2024-06-10 13:45:23"}
}
}
4. Annotation của Gson: kiểm soát tuần tự hóa
Gson hỗ trợ các annotation để kiểm soát chi tiết hơn quá trình tuần tự hóa và giải tuần tự.
@SerializedName — đổi tên trường
Nếu bạn muốn tên trường trong JSON khác đi, hãy dùng @SerializedName:
import com.google.gson.annotations.SerializedName;
public class User {
@SerializedName("full_name")
private String name;
private int age;
private boolean active;
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
Bây giờ khi tuần tự hóa, trường sẽ có tên full_name:
User user = new User("Diana", 28, true);
String json = new Gson().toJson(user);
// {"full_name":"Diana","age":28,"active":true}
@Expose — chỉ tuần tự hóa các trường được đánh dấu
Nếu bạn muốn chỉ tuần tự hóa một số trường nhất định, hãy dùng @Expose và cấu hình Gson:
import com.google.gson.annotations.Expose;
public class User {
@Expose
private String name;
@Expose
private int age;
private boolean active; // không được tuần tự hóa
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
Tạo Gson với hỗ trợ @Expose:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
User user = new User("Eve", 21, false);
String json = gson.toJson(user);
// {"name":"Eve","age":21}
@Since/@Until — tuần tự hóa có điều kiện theo phiên bản
Bạn có thể dùng @Since và @Until để chỉ tuần tự hóa các trường cho những phiên bản nhất định (ít dùng trong thực tế, nhưng nên biết).
5. Đặc điểm và hạn chế của Gson
Làm việc với đối tượng lồng nhau
Gson làm việc rất tốt với các đối tượng lồng nhau:
public class Profile {
private User user;
private String bio;
public Profile(User user, String bio) {
this.user = user;
this.bio = bio;
}
}
Profile profile = new Profile(new User("Frank", 27, true), "Java developer");
String json = new Gson().toJson(profile);
// {"user":{"name":"Frank","age":27,"active":true},"bio":"Java developer"}
Làm việc với collection
Với việc tuần tự hóa collection (List, Map) thì không có vấn đề, nhưng để giải tuần tự hãy dùng TypeToken (xem ở trên).
Hạn chế của Gson so với Jackson
- Không hỗ trợ các lớp Java record (cho tới những phiên bản gần đây)
- Hỗ trợ hạn chế các API kiểu ngày giờ mới (ví dụ, LocalDate, LocalDateTime — cần adapter tùy chỉnh)
- Không làm việc với các annotation của Jackson
- Không hỗ trợ cấu trúc đa hình phức tạp “out of the box”
- Không hỗ trợ tự động tham chiếu hai chiều (tính chu kỳ)
Adapter tùy chỉnh (TypeAdapter)
Nếu khả năng mặc định là chưa đủ, bạn có thể viết adapter riêng cho việc tuần tự hóa/giải tuần tự các kiểu phức tạp.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
public class BooleanAsIntAdapter extends TypeAdapter<Boolean> {
@Override
public void write(JsonWriter out, Boolean value) throws IOException {
out.value(value ? 1 : 0);
}
@Override
public Boolean read(JsonReader in) throws IOException {
return in.nextInt() == 1;
}
}
// Cách dùng:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
.create();
6. Thực hành: tuần tự hóa và giải tuần tự với cấu hình
Hãy mở rộng ứng dụng học tập của bạn và thêm chức năng lưu/đọc danh sách người dùng ở định dạng JSON.
Lớp User với annotation
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
public class User {
@Expose
@SerializedName("full_name")
private String name;
@Expose
private int age;
private boolean active; // không được tuần tự hóa
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// getter, setter...
}
Lưu danh sách người dùng ra JSON
import java.util.List;
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class SaveUsers {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Ivan", 23, true),
new User("Olga", 19, false)
);
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setPrettyPrinting()
.create();
String json = gson.toJson(users);
System.out.println(json);
/*
[
{
"full_name": "Ivan",
"age": 23
},
{
"full_name": "Olga",
"age": 19
}
]
*/
}
}
Tải danh sách người dùng từ JSON
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
public class LoadUsers {
public static void main(String[] args) {
String json = "[{\"full_name\":\"Ivan\",\"age\":23},{\"full_name\":\"Olga\",\"age\":19}]";
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
Type userListType = new TypeToken<List<User>>(){}.getType();
List<User> users = gson.fromJson(json, userListType);
for (User user : users) {
System.out.println(user.getName() + " (" + user.getAge() + ")");
}
// Ivan (23)
// Olga (19)
}
}
7. So sánh Gson và Jackson
| Tiêu chí | Gson | Jackson |
|---|---|---|
| Mức độ dễ sử dụng | +++++ (rất dễ) | +++ (hơi phức tạp hơn) |
| Kích thước thư viện | Nhỏ | Lớn hơn |
| Tốc độ | Nhanh, nhưng hơi chậm hơn | Rất nhanh |
| Tính linh hoạt | Trung bình | Cao (nhiều tùy chỉnh hơn) |
| Hỗ trợ annotation | Của riêng nó (@SerializedName) | Của riêng nó (@JsonProperty và khác) |
| Hỗ trợ các kiểu mới | Hạn chế | Tuyệt vời (Java 8+, record) |
| Hỗ trợ Android | Tuyệt vời | Tốt, nhưng nặng hơn |
| Làm việc với ngày giờ | Chỉ thông qua adapter | Sẵn có |
| Đa hình | Bị hạn chế | Tùy chỉnh linh hoạt |
8. Các lỗi thường gặp khi làm việc với Gson
Lỗi #1: Không dùng TypeToken cho collection.
Nếu bạn giải tuần tự danh sách hoặc map, nhất định phải dùng TypeToken<>, nếu không bạn sẽ gặp lỗi lạ hoặc collection rỗng.
Lỗi #2: Không có constructor không tham số.
Gson có thể hoạt động mà không cần constructor mặc định, nhưng đôi khi khi giải tuần tự các đối tượng phức tạp, việc thiếu constructor không tham số có thể gây lỗi. Tốt nhất luôn thêm constructor không tham số nếu bạn dự định giải tuần tự.
Lỗi #3: Tên trường không khớp.
Nếu trong JSON trường có tên "full_name", còn trong lớp là "name", thì nếu không có annotation @SerializedName("full_name") trường sẽ không được ánh xạ và giá trị sẽ là null.
Lỗi #4: Vấn đề với trường private.
Gson có thể tuần tự hóa các trường private, nhưng nếu chỉ có trường private và không có getter/setter, đôi khi sẽ gặp vấn đề khi giải tuần tự. Tốt hơn nên dùng getter và setter.
Lỗi #5: Làm việc với ngày giờ.
Mặc định Gson tuần tự hóa Date ở định dạng không tiện. Với LocalDate, LocalDateTime và các kiểu mới khác, nếu không có adapter tùy chỉnh sẽ gặp lỗi tuần tự hóa.
Lỗi #6: Không dùng @Expose nhưng lại bật excludeFieldsWithoutExposeAnnotation().
Nếu bạn bật excludeFieldsWithoutExposeAnnotation() nhưng không đánh dấu các trường bằng @Expose, chúng sẽ không được tuần tự hóa và cũng không được giải tuần tự — kết quả sẽ là JSON trống hoặc các đối tượng có giá trị null.
GO TO FULL VERSION