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

时间:2017-01-04 14:47:40

标签: 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

1 个答案:

答案 0 :(得分:1)

给定节点的子节点似乎总是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键/值对委托给指定目标类型的反序列化上下文,从而进行递归反序列化,这将适用于任意嵌套元素级别(但是,GSON具有一些内部堆栈限制,即&#39;如果我没有弄错的话,限制为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