Gson:如何处理可能具有不同类型的字段?

我正在尝试使用Gson反序列化响应。 数据由可以嵌套到任意深度的节点列表组成。 json看起来像这样:

{ "type": "node", "children": [ { "id": "abc123", "name": "Name 1", "subdata": { "type": "node", "children": [ { "id": "def456", "name": "Name 2" } ] } } ] } 

现在,没有任何自定义类型适配器,我可以使用以下类:

 public class Data { private String type; private List nodes; } public class Node { private String id; private String name; private Data subdata; } 

现在一切都很好,花花公子。 但是,服务器可能会切断一些较深的节点并仅响应其ID,因此subdata可能如下所示:

 "subdata": { "type": "extra", "children": ["ghi", "jkl", "mno"] } 

这当然可以表示为这样的Java类:

 public class ExtraData { private String type; private List children; } 

但问题是:如何处理反序列化以使subdata可以是Data还是ExtraData

给定节点的子节点似乎总是JSON数组,所以你可以用它们做的第一件事是将子节点声明为List隐藏实际类型。 但是,您仍然可以使用type属性/字段来获取子项的实际类型。 最简单的方法可能就是添加另一个JSON反序列化器,以便以一些性能成本反序列化Data实例(因为它们不是类型适配器),据我所知,在Data类的字段上缺少@SerializedName

如果您也可以更改DTO类型,则更喜欢枚举而不是原始字符串,因为它们与枚举完美配合(特别是与智能IDE配合使用):

 enum Type { @SerializedName("node") NODE, @SerializedName("extra") EXTRA } 

Data类本身可能如下所示:

 final class Data { private final Type type; private final List children; // this one is supposed to be: // * either List if type=EXTRA // * or List if type=NODE Data(final Type type, final List children) { this.type = type; this.children = children; } Type getType() { return type; } List getChildren() { return children; } } 

由于extra子类只是你问题中的字符串,只需添加节点DTO类:

 final class Node { @SerializedName("id") private final String id = null; @SerializedName("name") private final String name = null; @SerializedName("subdata") private final Data subdata = null; String getId() { return id; } String getName() { return name; } Data getSubdata() { return subdata; } } 

现在,在反序列化Data类时,您可以确定子列表的实际类型,并根据节点类型将其反序列化为字符串列表或节点列表。 请注意,下面的反序列化器使用java.lang.reflect.Type实例而不是java.lang.Class因为后者由于Javagenerics类型擦除而List.class ,并且对于任何列表参数化(字符串,节点等)都是List.class 。 使用类型标记提供预期类型,只需将JSON键/值对委托给指定目标类型的反序列化上下文,从而进行适用于任意嵌套元素级别的递归反序列化(但是,GSON有一些内部堆栈限制,限制为32如果我没错的话)。

 final class DataJsonDeserializer implements JsonDeserializer { private static final JsonDeserializer dataJsonDeserializer = new DataJsonDeserializer(); private static final java.lang.reflect.Type nodeListType = new TypeToken>() { }.getType(); private static final java.lang.reflect.Type stringListType = new TypeToken>() { }.getType(); private DataJsonDeserializer() { } static JsonDeserializer getDataJsonDeserializer() { return dataJsonDeserializer; } @Override public Data deserialize(final JsonElement jsonElement, final java.lang.reflect.Type type, final JsonDeserializationContext context) throws JsonParseException { final JsonObject rootJsonObject = jsonElement.getAsJsonObject(); final Type nodeType = context.deserialize(rootJsonObject.get("type"), Type.class); final JsonArray childrenJsonArray = rootJsonObject.get("children").getAsJsonArray(); final List children; switch ( nodeType ) { case NODE: children = context.deserialize(childrenJsonArray, nodeListType); break; case EXTRA: children = context.deserialize(childrenJsonArray, stringListType); break; default: throw new AssertionError(nodeType); } return new Data(nodeType, children); } } 

以递归方式遍历子项的演示(请注意下面将每个项目强制转换为目标类型的增强for语句):

 public final class EntryPoint { private static final String JSON_WITH_SUBNODES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"node\",\"children\":[{\"id\":\"def456\",\"name\":\"Name 2\"}]}}]}"; private static final String JSON_WITH_REFERENCES = "{\"type\":\"node\",\"children\":[{\"id\":\"abc123\",\"name\":\"Name 1\",\"subdata\":{\"type\":\"extra\",\"children\":[\"ghi\",\"jkl\",\"mno\"]}}]}"; private static final Gson gson = new GsonBuilder() .registerTypeAdapter(Data.class, getDataJsonDeserializer()) .create(); public static void main(final String... args) { process(gson.fromJson(JSON_WITH_SUBNODES, Data.class)); process(gson.fromJson(JSON_WITH_REFERENCES, Data.class)); } private static void process(final Data data) { process(data, 0); out.println(); } private static void process(final Data data, final int level) { for ( int i = 0; i < level; i++ ) { out.print('>'); } final List children = data.getChildren(); final Type type = data.getType(); out.println(type); switch ( type ) { case NODE: @SuppressWarnings("unchecked") final Iterable nodeChildren = (Iterable) children; for ( final Node node : nodeChildren ) { out.printf("\t%s %s\n", node.getId(), node.getName()); final Data subdata = node.getSubdata(); if ( subdata != null ) { process(subdata, level + 1); } } break; case EXTRA: @SuppressWarnings("unchecked") final Iterable extraChildren = (Iterable) children; for ( final String extra : extraChildren ) { out.printf("\t%s\n", extra); } break; default: throw new AssertionError(type); } } } 

输出:

 NODE abc123 Name 1 >NODE def456 Name 2 NODE abc123 Name 1 >EXTRA ghi jkl mno