gpt4 book ai didi

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

转载 作者:搜寻专家 更新时间:2023-11-01 03:33:14 24 4
gpt4 key购买 nike

我正在尝试使用 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<Node> 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<String> children;
}

但问题是:我如何处理反序列化,以便 subdata 可以是 DataExtraData

最佳答案

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

如果您也愿意更改 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<String> if type=EXTRA
// * or List<Node> 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,因为后者由于 Java 通用类型删除而很弱,并且对于任何列表参数化(字符串、节点等)都是 List.class。有了类型标记提供的预期类型,只需将 JSON 键/值对委托(delegate)给指定目标类型的反序列化上下文,从而进行适用于任意嵌套元素级别的递归反序列化(但是,GSON 有一些内部堆栈限制,限制为 32如果我没记错的话)。

final class DataJsonDeserializer
implements JsonDeserializer<Data> {

private static final JsonDeserializer<Data> dataJsonDeserializer = new DataJsonDeserializer();

private static final java.lang.reflect.Type nodeListType = new TypeToken<List<Node>>() {
}.getType();

private static final java.lang.reflect.Type stringListType = new TypeToken<List<String>>() {
}.getType();

private DataJsonDeserializer() {
}

static JsonDeserializer<Data> 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<Node> nodeChildren = (Iterable<Node>) 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<String> extraChildren = (Iterable<String>) 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

关于java - Gson:如何处理可能具有不同类型的字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41466633/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com