Gson解析JSON数组转List方法详解
本文详解Gson反序列化时出现ClassCastException:CannotcastJsonPrimitivetoJsonObject的根本原因,重点解决ChatColor类型不匹配、缺失字段及JSON结构误判等问题,并提供可直接复用的修复方案。

本文详解 Gson 反序列化时出现 ClassCastException: Cannot cast JsonPrimitive to JsonObject 的根本原因,重点解决 ChatColor 类型不匹配、缺失字段及 JSON 结构误判等问题,并提供可直接复用的修复方案。
本文详解 Gson 反序列化时出现 `ClassCastException: Cannot cast JsonPrimitive to JsonObject` 的根本原因,重点解决 `ChatColor` 类型不匹配、缺失字段及 JSON 结构误判等问题,并提供可直接复用的修复方案。
该错误看似发生在 Gson 解析阶段(fromJson(...)),实则根源在于 Java 类型定义与 JSON 数据结构严重不一致。从你提供的 JSON 示例 ["{...}"] 可知,API 返回的是标准 JSON 数组,但 Gson 在尝试将其中某个元素(如字符串 "WHITE")强制转为 JsonObject 时失败——这通常意味着 Gson 正在解析一个本应是对象(JsonObject)的位置,却遇到了原始值(JsonPrimitive),典型诱因有以下三点:
? 根本原因分析
ChatColor color 字段类型失配
JSON 中 "color": "WHITE" 是字符串字面量,而你的 Rank 类中声明为 ChatColor(Spigot 的枚举类)。Gson 默认无法自动将字符串 "WHITE" 映射为 ChatColor.WHITE,若未注册自定义 TypeAdapter,Gson 会尝试以泛型方式解析该字段,最终因类型擦除和内部反射逻辑错乱,触发 JsonPrimitive → JsonObject 的非法强转。inheritFromId 字段缺失且无默认值
JSON 示例中未包含 "inheritFromId" 字段,而 Rank 构造器为全参 @AllArgsConstructor,Lombok 会生成要求所有字段非 null 的构造函数。Gson 在反序列化时若找不到对应键,将传入 null,导致后续构造失败或空指针异常(日志末尾的 NullPointerException 已佐证)。JSON 响应体可能非纯数组(隐藏陷阱)
虽然你确认返回的是 [{"id":"default",...}],但需严格验证 getJsonResponse() 方法是否始终返回合法 JSON 数组字符串。常见错误包括:- API 错误时返回 {"error":"..."}(单个对象而非数组)
- 网络异常返回空字符串或 HTML 错误页
- getJsonResponse() 内部逻辑意外截断/拼接了非 JSON 内容
✅ 正确解决方案
步骤 1:为 ChatColor 注册 TypeAdapter(关键!)
// 创建 Gson 实例时注入适配器
Gson gson = new GsonBuilder()
.registerTypeAdapter(ChatColor.class, new JsonDeserializer() {
@Override
public ChatColor deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) return null;
String colorName = json.getAsString().toUpperCase();
try {
return ChatColor.valueOf(colorName);
} catch (IllegalArgumentException e) {
// 处理未知颜色名,例如回退到 WHITE 或抛出定制异常
return ChatColor.WHITE;
}
}
})
.create(); 步骤 2:修正 Rank 类结构(兼容缺失字段)
@Getter
@NoArgsConstructor // 必须添加无参构造器,Gson 反序列化必需
@AllArgsConstructor(access = AccessLevel.PRIVATE) // 私有全参构造,避免外部滥用
public class Rank {
private String id;
private String displayName;
private String prefix;
private String inheritFromId; // 改为 String 类型(更安全),或设为 @Nullable
private ChatColor color;
private List permissions;
private int weight;
private boolean staff;
// 若需保留继承关系,可添加带默认值的构造器
public Rank(String id, String displayName, String prefix, String inheritFromId,
ChatColor color, List permissions, int weight, boolean staff) {
this.id = id;
this.displayName = displayName;
this.prefix = prefix;
this.inheritFromId = inheritFromId != null ? inheritFromId : ""; // 安全默认值
this.color = color;
this.permissions = permissions != null ? permissions : new ArrayList<>();
this.weight = weight;
this.staff = staff;
}
} 步骤 3:健壮的反序列化调用(含错误防护)
public void refresh() {
String jsonResponse = Juke.INSTANCE.getApiHandler().request("/ranks").getJsonResponse();
// 强制校验 JSON 合法性(防御性编程)
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
throw new IllegalStateException("API returned empty or null JSON response");
}
try {
// 使用预配置的 Gson 实例
Type listType = new TypeToken>() {}.getType();
this.ranks = gson.fromJson(jsonResponse, listType);
// 可选:验证解析结果
if (this.ranks == null) {
throw new IllegalStateException("Gson failed to parse JSON into List");
}
} catch (JsonSyntaxException e) {
throw new RuntimeException("Invalid JSON syntax in /ranks response: " + jsonResponse, e);
} catch (JsonParseException e) {
throw new RuntimeException("Failed to parse Rank objects: " + e.getMessage(), e);
}
}
⚠️ 注意事项
- 切勿依赖 @AllArgsConstructor + @Getter 的组合进行反序列化:Gson 需要无参构造器或字段级访问权限,Lombok 的全参构造器会阻止 Gson 的默认字段注入机制。
- 始终验证 API 响应内容:在 getJsonResponse() 中添加日志输出原始字符串,确认其真实结构(尤其注意引号、括号、编码问题)。
- 版本兼容性提示:Gson 2.8.6 与 2.10.1 均存在此问题,本质与版本无关,而是类型契约不一致所致;升级 Gson 并不能绕过类型映射缺陷。
通过以上三步改造,即可彻底解决 ClassCastException,并构建出稳定、可维护的 JSON 反序列化流程。


































