Java中使用Gson处理JSON多类型children字段的规范实现方式
Hey,我完全懂你现在的困扰——用字符串替换"kids"为"children"确实是个临时凑活的办法,但正如你说的,一旦数据里真的出现了"kids"这个词(比如某个节点的text是"Kids' Zone"),替换之后就彻底乱套了,而且这种方案完全不具备扩展性。
其实Gson本身就提供了完美解决这种多类型字段问题的机制,咱们直接用官方推荐的方式来搞定:
首先,咱们先把Data类里多余的kids字段删掉,只保留children列表——毕竟咱们要的是一个字段对应两种类型,不是两个字段来回替换:
public class Data { private String id; private String text; private String icon; private State state; private List<Data> children; // 所有getter、setter方法保持不变,去掉原来的kids相关方法 }
接下来写一个自定义的JsonSerializer,用来控制Data对象转JSON时的逻辑:当children为空或者null时,输出布尔值false;当有子节点时,输出数组。
public class DataSerializer implements JsonSerializer<Data> { @Override public JsonElement serialize(Data src, Type typeOfSrc, JsonSerializationContext context) { JsonObject jsonObject = new JsonObject(); // 序列化基础字段 jsonObject.addProperty("id", src.getId()); jsonObject.addProperty("text", src.getText()); if (src.getIcon() != null) { jsonObject.addProperty("icon", src.getIcon()); } if (src.getState() != null) { jsonObject.add("state", context.serialize(src.getState())); } // 核心:处理children字段的多类型逻辑 if (src.getChildren() == null || src.getChildren().isEmpty()) { jsonObject.addProperty("children", false); } else { jsonObject.add("children", context.serialize(src.getChildren())); } return jsonObject; } }
如果还需要处理JSON转Data对象的反序列化(比如后端返回的JSON里children可能是布尔值或数组),再写一个JsonDeserializer:
public class DataDeserializer implements JsonDeserializer<Data> { @Override public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); Data data = new Data(); // 反序列化基础字段 data.setId(jsonObject.get("id").getAsString()); data.setText(jsonObject.get("text").getAsString()); if (jsonObject.has("icon")) { data.setIcon(jsonObject.get("icon").getAsString()); } if (jsonObject.has("state")) { data.setState(context.deserialize(jsonObject.get("state"), State.class)); } // 核心:处理children字段的两种类型 JsonElement childrenElement = jsonObject.get("children"); if (childrenElement.isJsonPrimitive() && childrenElement.getAsJsonPrimitive().isBoolean()) { // 如果是布尔值,这里可以根据业务需求设置children为null或空列表 data.setChildren(null); } else if (childrenElement.isJsonArray()) { data.setChildren(context.deserialize(childrenElement, new TypeToken<List<Data>>(){}.getType())); } return data; } }
最后,在创建Gson实例的时候注册这两个适配器:
public static String createFolderJsonData() { CreateFolderTree cft = new CreateFolderTree(null); Data treeData = cft.start(1); Gson gson = new GsonBuilder() .registerTypeAdapter(Data.class, new DataSerializer()) .registerTypeAdapter(Data.class, new DataDeserializer()) .create(); return gson.toJson(treeData); }
如果觉得每次创建Gson都要注册适配器太麻烦,可以直接用@JsonAdapter注解把适配器和Data类绑定,写一个集成读写逻辑的TypeAdapter:
首先给Data类加注解:
@JsonAdapter(DataAdapter.class) public class Data { // 字段和getter/setter不变 }
然后写DataAdapter:
public class DataAdapter extends TypeAdapter<Data> { private final Gson gson = new Gson(); @Override public void write(JsonWriter out, Data value) throws IOException { out.beginObject(); // 写入基础字段 out.name("id").value(value.getId()); out.name("text").value(value.getText()); if (value.getIcon() != null) { out.name("icon").value(value.getIcon()); } if (value.getState() != null) { out.name("state"); gson.toJson(value.getState(), State.class, out); } // 处理children字段 if (value.getChildren() == null || value.getChildren().isEmpty()) { out.name("children").value(false); } else { out.name("children"); gson.toJson(value.getChildren(), new TypeToken<List<Data>>(){}.getType(), out); } out.endObject(); } @Override public Data read(JsonReader in) throws IOException { Data data = new Data(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "id": data.setId(in.nextString()); break; case "text": data.setText(in.nextString()); break; case "icon": data.setIcon(in.nextString()); break; case "state": data.setState(gson.fromJson(in, State.class)); break; case "children": JsonToken token = in.peek(); if (token == JsonToken.BOOLEAN) { in.nextBoolean(); data.setChildren(null); } else if (token == JsonToken.BEGIN_ARRAY) { data.setChildren(gson.fromJson(in, new TypeToken<List<Data>>(){}.getType())); } break; default: in.skipValue(); // 跳过未知字段,避免解析失败 break; } } in.endObject(); return data; } }
这样使用的时候直接用new Gson()就行,不需要额外注册:
public static String createFolderJsonData() { CreateFolderTree cft = new CreateFolderTree(null); Data treeData = cft.start(1); return new Gson().toJson(treeData); }
除了你提到的“误替换真实数据中的'kids'”问题,还有这些坑:
- 如果JSON结构复杂,嵌套层级深,字符串替换可能会破坏其他字段的格式
- 完全不具备类型安全性,一旦业务逻辑变化(比如children需要支持第三种类型),根本没法扩展
- 代码可读性差,其他维护者看到replace操作会一脸懵,不知道背后的业务逻辑
这种自定义适配器的方式才是Gson官方推荐的多类型字段处理方案,不仅能精准控制序列化/反序列化逻辑,还能保证代码的可维护性和扩展性。
内容的提问来源于stack exchange,提问作者Paul Taylor

