JSON 解析:Gson 与 org.json
系统对比 Gson 声明式反序列化与 org.json 手动解析,涵盖 @SerializedName、TypeToken 泛型、嵌套集合、容错策略与适用场景。
学习目标
- 掌握 Gson 的 fromJson/toJson 基本用法
- 理解 @SerializedName、字段映射、忽略字段
- 能够用 TypeToken 解析泛型集合与嵌套结构
- 学会 org.json 手动解析,并能区分两者适用场景
- 掌握 JSON 解析的容错与错误处理
学习目标
- 掌握 Gson 反序列化(
fromJson)与序列化(toJson)的基本用法。 - 理解
@SerializedName、字段忽略、@Expose、空值策略。 - 能够用
TypeToken解析泛型集合与嵌套结构。 - 学会用
org.json的JSONObject/JSONArray手动解析,并区分两者适用场景。 - 掌握 JSON 解析的容错与错误处理。
Gson 与 org.json JSON 解析实战
1. 依赖
Gson 已被 Retrofit converter 默认带入;如直接使用,需要单独声明。org.json 是 Android 系统库,无需引入。
android {
compileSdk 34
}
dependencies {
implementation 'com.google.code.gson:gson:2.11.0'
}
2. Gson 基础
Gson 通过反射把 JSON 与 Java 对象互转,模型字段名建议与 JSON key 一致,不一致时用 @SerializedName 显式映射。
package com.example.json.model;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class Article {
@SerializedName("id")
private int id;
// 字段名与 JSON key 不同时使用 @SerializedName
@SerializedName("title_text")
private String title;
@SerializedName("author")
private Author author;
@SerializedName("tags")
private List<String> tags;
// 不参与序列化/反序列化的字段
@Expose(serialize = false, deserialize = false)
private transient String localCache;
// 没有对应 JSON key 时,反序列化保持默认值(如 false)
private boolean liked;
// getter / setter 省略
public int getId() { return id; }
public String getTitle() { return title; }
}
public class Author {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
@SerializedName("avatar_url")
private String avatarUrl;
public String getName() { return name; }
}
3. fromJson / toJson 基本用法
Gson gson = new Gson();
// 反序列化对象
String json = "{\"id\":1,\"title_text\":\"Hello\",\"author\":{\"id\":2,\"name\":\"Tom\"}}";
Article article = gson.fromJson(json, Article.class);
// 序列化对象
String out = gson.toJson(article);
// 美化输出
Gson pretty = new GsonBuilder().setPrettyPrinting().create();
String prettyJson = pretty.toJson(article);
// 处理 null:序列化时不输出值为 null 的字段
Gson noNulls = new GsonBuilder().serializeNulls().create();
4. 集合与泛型:TypeToken
Java 泛型在运行期被擦除,直接传 List<Article>.class 是非法的。Gson 借助 TypeToken 在编译期保留泛型信息:
String json = "[{\"id\":1,\"title\":\"A\"},{\"id\":2,\"title\":\"B\"}]";
// 错误写法:会被解析成 List<LinkedTreeMap> 而非 List<Article>
// List<Article> list = gson.fromJson(json, List.class);
// 正确写法:通过 TypeToken 保留泛型
TypeToken<List<Article>> token = new TypeToken<List<Article>>() {};
List<Article> articles = gson.fromJson(json, token.getType());
// Map 解析
TypeToken<Map<String, Article>> mapToken = new TypeToken<Map<String, Article>>() {};
Map<String, Article> map = gson.fromJson(jsonMap, mapToken.getType());
5. 复杂嵌套与数组
public class ArticleListResult {
@SerializedName("total")
private int total;
@SerializedName("page")
private int page;
@SerializedName("data")
private List<Article> data;
@SerializedName("aggregations")
private Map<String, Integer> aggregations;
public List<Article> getData() { return data; }
}
ArticleListResult result = gson.fromJson(raw, ArticleListResult.class);
6. 通用分页响应解析
服务端常见 ApiResponse<T> 包装结构,使用泛型 TypeToken 一次解析:
public class ApiResponse<T> {
@SerializedName("code")
private int code;
@SerializedName("message")
private String message;
@SerializedName("data")
private T data;
public T getData() { return data; }
public int getCode() { return code; }
}
// 用 TypeToken 保留 T 的实际类型
Type type = TypeToken.getParameterized(ApiResponse.class, Article.class).getType();
ApiResponse<Article> resp = gson.fromJson(json, type);
if (resp.getCode() != 0) {
throw new ApiException(resp.getCode(), resp.getMessage());
}
Article article = resp.getData();
7. 容错策略:Lenient 与 JsonNull
服务端有时返回字段类型不稳定(如某次把 number 写成 string),Gson 默认严格模式会抛 JsonSyntaxException。可在 GsonBuilder 中开启容错:
Gson lenient = new GsonBuilder()
.setLenient() // 容错模式
.serializeNulls() // 序列化 null
.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) // 统一命名
.registerTypeHierarchyAdapter(Number.class, new NumberTypeAdapter())
.create();
自定义适配器把字符串数字转回数值:
public class NumberTypeAdapter implements JsonDeserializer<Number> {
@Override
public Number deserialize(JsonElement json, Type type, JsonDeserializationContext c)
throws JsonParseException {
try {
return json.getAsNumber();
} catch (Exception e) {
String s = json.getAsString();
return s.isEmpty() ? 0 : new BigDecimal(s);
}
}
}
8. org.json 手动解析
org.json 是 Android 内置的轻量 JSON 库,提供 JSONObject / JSONArray / JSONTokener 三类核心类。没有反射、不生成中间对象模型,性能较高且可控,适合字段稀疏、结构不稳定或只需取几个字段的场景。
String json = "{\"code\":0,\"data\":{\"id\":1,\"tags\":[\"a\",\"b\"]}}";
try {
JSONObject root = new JSONObject(json);
int code = root.optInt("code", -1);
if (code != 0) return;
JSONObject data = root.optJSONObject("data");
if (data == null) return;
int id = data.getInt("id");
JSONArray tags = data.optJSONArray("tags");
if (tags != null) {
for (int i = 0; i < tags.length(); i++) {
String tag = tags.optString(i, "");
Log.d("TAG", "tag=" + tag);
}
}
} catch (JSONException e) {
Log.e("TAG", "parse failed", e);
}
API 选用原则:
getXxx(key)找不到时抛JSONException,适合必填字段。optXxx(key, default)找不到时返回默认值,适合可选字段。optJSONObject/optJSONArray找不到返回null,使用前需判空。
9. JSONTokener 严格区分类型
有时服务端返回的同一字段可能是对象或数组(例如只一条时返回对象),可用 JSONTokener 先探测类型:
Object any = new JSONTokener(raw).nextValue();
if (any instanceof JSONObject) {
Article one = gson.fromJson(raw, Article.class);
} else if (any instanceof JSONArray) {
List<Article> list = gson.fromJson(raw,
new TypeToken<List<Article>>() {}.getType());
}
10. Gson vs org.json 对比
| 维度 | Gson | org.json |
|---|---|---|
| 编程模型 | 声明式(POJO) | 命令式(手动取 key) |
| 模型维护 | 需写类,扩展方便 | 无需类,灵活但啰嗦 |
| 性能 | 反射开销,稍慢 | 解析快,无反射 |
| 容错 | @SerializedName + Adapter |
optXxx 自带默认值 |
| 大数据流 | 改用 Gson Streaming(JsonReader) | 自带流式 API |
| 适用场景 | 字段稳定、复杂结构 | 字段稀疏、结构多变、调试 |
11. 错误处理与容错
public static <T> T safeFromJson(Gson gson, String json, Type type, T def) {
try {
T result = gson.fromJson(json, type);
return result == null ? def : result;
} catch (JsonSyntaxException | JsonParseException e) {
Log.e("JSON", "parse error: " + e.getMessage());
return def;
}
}
// 调用
List<Article> list = safeFromJson(gson, json,
new TypeToken<List<Article>>() {}.getType(), Collections.emptyList());
实践建议:
- 网络响应解析必须 try-catch,避免把异常直接抛到主线程。
- 必填字段缺失应抛业务异常,可选字段走
optXxx静默兜底。 - 大数据(>1MB)优先用
JsonReader流式读取,避免一次性JSONObject占内存。
常见坑与最佳实践
- 泛型擦除导致 List 反序列化为
LinkedTreeMap:必须用new TypeToken<List<T>>(){}.getType()。 @SerializedName拼写错误不会报错:解析失败时该字段保持默认值,难定位。建议结合Lenient与单元测试覆盖。transient关键字会同时屏蔽序列化与反序列化,要单独控制时用@Expose(serialize=..., deserialize=...)并构造excludeFieldsWithoutExposeAnnotation()的 Gson。- Date 默认按
Sep 25, 2026 12:00:00 AM格式输出,与服务端不一致时使用setDateFormat("yyyy-MM-dd HH:mm:ss")。 - Gson 单例不要并发修改配置:
GsonBuilder创建后不可变;如果需要不同容错策略,建多个实例。 JSONObject.toString()多次拼接字符串性能差,大量数据应直接序列化 POJO。optInt("count")没传默认值时返回 0,可能掩盖真实缺失,建议显式传optInt(key, -1)。- 服务端返回
null字段在 Gson 中赋 null,若字段为基本类型 int/boolean,反序列化保持 0/false,不能区分「字段缺失」与「字段值为 0」。
章节小结
- Gson 通过 POJO + 反射实现声明式解析,配合
@SerializedName、TypeToken可处理任意泛型结构。 GsonBuilder提供命名策略、日期格式、Lenient、自定义适配器等扩展点。org.json内置无依赖,适合字段稀疏、结构多变的场景,注意getXxx与optXxx的取舍。- 网络层必须对解析异常做兜底,避免把
JsonSyntaxException暴露到 UI。
下一章预告
解析后的对象需要持久化以便离线使用。下一章《数据存储:SharedPreferences》将讲解轻量 KV 存储的标准用法、用 Gson 存复杂对象的封装技巧、线程安全以及向 DataStore 迁移的思路。