A
第 21 章JAVA35 分钟

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 占内存。

常见坑与最佳实践

  1. 泛型擦除导致 List 反序列化为 LinkedTreeMap:必须用 new TypeToken<List<T>>(){}.getType()。
  2. @SerializedName 拼写错误不会报错:解析失败时该字段保持默认值,难定位。建议结合 Lenient 与单元测试覆盖。
  3. transient 关键字会同时屏蔽序列化与反序列化,要单独控制时用 @Expose(serialize=..., deserialize=...) 并构造 excludeFieldsWithoutExposeAnnotation() 的 Gson。
  4. Date 默认按 Sep 25, 2026 12:00:00 AM 格式输出,与服务端不一致时使用 setDateFormat("yyyy-MM-dd HH:mm:ss")。
  5. Gson 单例不要并发修改配置:GsonBuilder 创建后不可变;如果需要不同容错策略,建多个实例。
  6. JSONObject.toString() 多次拼接字符串性能差,大量数据应直接序列化 POJO。
  7. optInt("count") 没传默认值时返回 0,可能掩盖真实缺失,建议显式传 optInt(key, -1)。
  8. 服务端返回 null 字段在 Gson 中赋 null,若字段为基本类型 int/boolean,反序列化保持 0/false,不能区分「字段缺失」与「字段值为 0」。

章节小结

  • Gson 通过 POJO + 反射实现声明式解析,配合 @SerializedName、TypeToken 可处理任意泛型结构。
  • GsonBuilder 提供命名策略、日期格式、Lenient、自定义适配器等扩展点。
  • org.json 内置无依赖,适合字段稀疏、结构多变的场景,注意 getXxx 与 optXxx 的取舍。
  • 网络层必须对解析异常做兜底,避免把 JsonSyntaxException 暴露到 UI。

下一章预告

解析后的对象需要持久化以便离线使用。下一章《数据存储:SharedPreferences》将讲解轻量 KV 存储的标准用法、用 Gson 存复杂对象的封装技巧、线程安全以及向 DataStore 迁移的思路。