Giải quyết triệt để lỗi phân tích JSON với Gson
Khi triển khai ứng dụng Java, lỗi JsonSyntaxException từ Gson thường gây sập hệ thống đột ngột. Dữ liệu không chuẩn từ API bên ngoài là nguyên nhân phổ biến. Bài viết này cung cấp phương pháp hệ thống để xử lý lỗi, xây dựng lớp xử lý JSON robust và đảm bảo an toàn dữ liệu.
Bản chất lỗi phân tích JSON
Gson sử dụng hai loại lỗi chính: JsonSyntaxException và JsonParseException. Dù biểu hiện tương tự, nguyên nhân và cách xử lý hoàn toàn khác nhau.
JsonSyntaxException: Lỗi cấu trúc cú pháp
Xảy ra khi chuỗi JSON không tuân thủ chuẩn. Ví dụ:
- Thiếu dấu ngoặc đóng:
{"name":"Alice - Giá trị số không hợp lệ:
{"age": "hai mươi"} - Ký tự escape sai:
{"path":"C:\\\\Documents"}
Trong mã nguồn, lớp này có 3 phương thức khởi tạo:
public JsonSyntaxException(String msg) { super(msg); }
public JsonSyntaxException(String msg, Throwable cause) { super(msg, cause); }
public JsonSyntaxException(Throwable cause) { super(cause); }
JsonParseException: Lỗi chuyển đổi dữ liệu
Là lớp cơ sở cho mọi lỗi phân tích. Xảy ra khi JSON hợp lệ nhưng không thể chuyển thành đối tượng Java. Ví dụ:
- Chuyển mảng JSON thành đối tượng đơn
- Chuyển đổi kiểu không thành công (ví dụ: chuỗi thành Date)
- Bộ chuyển đổi tùy chỉnh ném ngoại lệ
Gson thiết kế JsonParseException như runtime exception để buộc xử lý lỗi, không bỏ qua.
Chẩn đoán lỗi hệ thống
Quy trình 3 bước khi gặp lỗi:
- Dùng
instanceofxác định loại lỗi - Phân tích stack trace để定位 lỗi
- Kiểm tra dữ liệu đầu vào bằng công cụ như JSONLint
try {
// Logic phân tích
} catch (JsonSyntaxException e) {
// Xử lý cú pháp
} catch (JsonParseException e) {
// Xử lý cấu trúc
}
Giải pháp nâng cao
Xử lý cơ bản
public <T> T xử_lý_an_toàn(String dữ_liệu, Class<T> kiểu) {
try {
return new Gson().fromJson(dữ_liệu, kiểu);
} catch (JsonSyntaxException e) {
logger.error("Lỗi cú pháp: {} - Dữ liệu: {}", e.getMessage(), dữ_liệu, e);
return null;
} catch (JsonParseException e) {
logger.error("Lỗi chuyển đổi: {} - Kiểu: {}", e.getMessage(), kiểu.getName(), e);
return null;
}
}
Bộ chuyển đổi linh hoạt
public class AdapterGiáDẻoDai extends TypeAdapter<BigDecimal> {
@Override
public void write(JsonWriter writer, BigDecimal giá) throws IOException {
writer.value(giá);
}
@Override
public BigDecimal read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.STRING) {
try {
return new BigDecimal(reader.nextString());
} catch (NumberFormatException ex) {
reader.skipValue();
logger.warn("Dùng giá mặc định 0", ex);
return BigDecimal.ZERO;
}
} else if (reader.peek() == JsonToken.NUMBER) {
return reader.nextBigDecimal();
}
reader.skipValue();
return BigDecimal.ZERO;
}
}
Đăng ký adapter:
Gson gson = new GsonBuilder()
.registerTypeAdapter(BigDecimal.class, new AdapterGiáDẻoDai())
.create();
Phân tích trước dữ liệu
Dùng JSON Schema để validate trước khi parse:
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
JsonSchema schema = factory.getSchema(new File("schema.json"));
Set<ValidationMessage> lỗi = schema.validate(jsonNode);
if (!lỗi.isEmpty()) {
throw new InvalidDataException("Dữ liệu không hợp lệ: " + lỗi.iterator().next().getMessage());
}
Kinh nghiệm thực tế
Xử lý ngày tháng
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
.registerTypeAdapter(Date.class, new AdapterNgàyUTC())
.create();
Xử lý số
- Dùng
Longthayintđể tránh tràn - Dùng
BigDecimalthaydoublecho độ chính xác - Cho trường nullable dùng wrapper type
Đa dạng đối tượng
RuntimeTypeAdapterFactory<PhươngTiện> adapter = RuntimeTypeAdapterFactory
.of(PhươngTiện.class, "loại")
.registerSubtype(XeMay.class, "xe_may")
.registerSubtype(XeOto.class, "xe_oto");
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(adapter)
.create();
Giám sát lỗi
Thu thập chỉ số:
- Tỷ lệ thành công phân tích
- Phân bố loại lỗi
- Nguồn lỗi từ API
- Mẫu lỗi phổ biến
Ví dụ: Nền tảng thương mại điện tử xử lý weight字段 khi đôi khi là số, đôi khi là chuỗi. Xây dựng Adapter tự động loại bỏ đơn vị, giảm lỗi từ 3.2% xuống 0.05%.