Gson:如何处理可能具有不同类型的字段? [英] Gson: How to handle a field that may have a different type?

查看:106
本文介绍了Gson:如何处理可能具有不同类型的字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

  public class Data {
私人字符串类型;
私人列表< Node>节点;
}

public class Node {
private String id;
私人字符串名称;
私人数据子数据;
}

现在一切正常。但是,服务器可能会切断一些更深层的节点,并仅使用它们的ID进行响应,因此子数据可能看起来像这样:

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

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

  public class ExtraData {
private String type;
私人列表< String>儿童;
}

问题是,我该如何处理反序列化, code> subdata 可以是 Data ExtraData

解决方案

给定节点的孩子总是看起来像是JSON数组,所以你可以用它们做的第一件事就是将孩子声明为 List<?> 隐藏实际的类型。但是,您仍然有类型属性/字段,这对于获取子类的实际类型来说是非常好的。最简单的方法可能就是添加另一个JSON反序列化器,以反序列化 Data 实例,并带来一些性能成本(因为它们不是类型适配器),并且据我所知,缺乏 @SerializedName Data 类的字段上。

如果您在更改DTO类型方面也很好,那么更喜欢枚举而不是原始字符串,因为它们与枚举一起完美工作(特别是与智能IDE合作时):

 枚举类型{

@SerializedName(node)
节点,

@ SerializedName(extra)
EXTRA

}

Data 类本身可能如下所示:

  final class Data {

private final Type type;
private final List<?>儿童; //这个应该是:
// * * List< String>如果type = EXTRA
// *或List< Node>如果type = NODE

Data(final类型,final List<> children){
this.type = type;
this.children = children;
}

类型getType(){
返回类型;
}

列表<?> getChildren(){
返回儿童;
}

}

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


$ b

  final类节点{

@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
实现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(){
返回dataJsonDeserializer;

$ b @Override
public Data deserialize(final JsonElement jsonElement,final java.lang.reflect.Type type,final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject rootJsonObject = jsonElement.getAsJsonObject();
final类型nodeType = context.deserialize(rootJsonObject.get(type),Type.class);
final JsonArray childrenJsonArray = rootJsonObject.get(children)。getAsJsonArray();
final List<?>儿童;
switch(nodeType){
case NODE:
children = context.deserialize(childrenJsonArray,nodeListType);
休息;
case EXTRA:
children = context.deserialize(childrenJsonArray,stringListType);
休息;
默认值:
抛出新的AssertionError(nodeType);
}
返回新的数据(nodeType,children);
}

}

递归遍历的演示(请注意,下面的每个项目转换为目标类型的语句增强了):

  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 \名称\:\名称1 \,\子数据\:{\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));
进程(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( '>');
}
最终列表<?> children = data.getChildren();
final类型type = data.getType();
out.println(type);
switch(type){
case NODE:
@SuppressWarnings(unchecked)
final Iterable< Node> nodeChildren =(Iterable< Node>)子节点;
for(final Node node:nodeChildren){
out.printf(\t%s%s\\\
,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\\\
,extra);
}
break;
默认值:
抛出新的AssertionError(type);
}
}

}

输出:

  NODE 
abc123名称1
>节点
def456名称2

节点
abc123名称1
>额外
ghi
jkl
mno


I'm trying to deserialize a response using Gson. The data consists of lists of nodes which may be nested to arbitrary depths. The json looks something like this:

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

Now, without any custom type adapters, I can make this work with the following classes:

public class Data {
    private String type;
    private List<Node> nodes;
}

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

Everything works fine and dandy for now. However, the server might cut some of the deeper nodes and respond only with their IDs, so the subdata might look like this instead:

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

This of course could be represented as a Java class like this:

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

The question is, though: How do I handle the deserialization so that the subdata could be either Data or ExtraData?

解决方案

The children of the given nodes seem to be JSON arrays always, so the first thing you could do with them is declaring the children as List<?> hiding the actual type. However, you still have the type property/field that is perfectly fine to get the actual type of the children. The simplest way is probably just adding another JSON deserializer in order to deserialize Data instances with some performance costs (since these are not type adapters) and, as far as I know, lack of @SerializedName on the fields of the Data class.

If you're also fine with changing your DTOs types, prefer enums rather than raw strings as they work just perfect with enums (especially in cooperation with smart IDEs):

enum Type {

    @SerializedName("node")
    NODE,

    @SerializedName("extra")
    EXTRA

}

The Data class itself then might look like as follows:

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;
    }

}

Since the extra-typed children are just strings in your question, just add the node DTO class:

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;
    }

}

Now, while deserializing the Data class, you can determine the actual type of the children list and, according to the node type, deserialize it as either a list of strings or a list of nodes. Note that the deserializer below uses java.lang.reflect.Type instances rather than java.lang.Class because the latter is weak due to Java generic type erasure and is List.class for any list parameterization (strings, nodes, etc). Having the expected types provided with type tokens, just delegate a JSON key/value pair to the deserialization context specifying the target type thus making recursive deserialization that would work for arbitrary nested elements level (however, GSON has some internal stack limit that's limited to 32 if I'm not mistaken).

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);
    }

}

And the demo that recursively walks through the children (note the enhanced for statements that cast each item to the target type below):

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);
        }
    }

}

The output:

NODE
    abc123 Name 1
>NODE
    def456 Name 2

NODE
    abc123 Name 1
>EXTRA
    ghi
    jkl
    mno

这篇关于Gson:如何处理可能具有不同类型的字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆