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

标签 java json gson deserialization

我正在尝试使用 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/

相关文章:

java - 关于关联的 Hibernate 自定义连接子句

json - 从 Postgres NOSQL/json 数据中搜索和选择

json - 使用带有字典数组的字典进行 Swift JSON 解析

java.lang.RuntimeException : Unable to invoke no-args constructor for class DBExecuter. Queryjson$valIOConfigclass

android - 是否可以在 Room 中忽略基本更新中的字段

java - 与 MacBook Pro 上的 64 位 Eclipse 不兼容的 JVM 错误消息

java - Java WatchService 是每个 JVM 还是一个应用程序可以启动多个?

java - 在java中使用gson解析json

java - Blowfish 加密问题

php - 在iphone应用程序中解析json数据