【问题标题】:Gson: How to handle a field that may have a different type?Gson:如何处理可能具有不同类型的字段?
【发布时间】:2017-01-04 14:47:39
【问题描述】:

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

【问题讨论】:

标签: java json gson deserialization


【解决方案1】:

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

如果您也可以更改 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-typed 子项只是您问题中的字符串,因此只需添加节点 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 有一些内部堆栈限制,限制为 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

【讨论】:

  • 非常感谢!这似乎正是我想要的。
猜你喜欢
  • 1970-01-01
  • 2017-01-30
  • 1970-01-01
  • 2022-01-19
  • 2015-10-23
  • 1970-01-01
  • 2021-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多