如何使用gson解析类层次结构? [英] How do I parse class hierarchies with gson?

查看:192
本文介绍了如何使用gson解析类层次结构?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个要与GSON相互转换的类层次结构.我不确定如何用GSON来解决这个问题(我目前有一个Factory类,它查看JSONObject,并根据是否存在键来调用正确的构造函数,该函数又将其某些工作委派给超级对象.班级).当我将这些对象存储在本地SQLite DB中时,我使用整数来表示它们的类型,而工厂类使用此类型来调用正确的构造函数.我在JSON中没有这个 type (不是我的).

如何根据JSON对象的内容告诉GSON为我实例化哪种类型的对象?

在以下示例中,将JSON括号内的...视为可能有或没有更多元素

这是类层次结构的细分:

有一个基本的抽象类型:SuperType具有JSON表示形式{"ct":12345,"id":"abc123 ...}

主要有2种抽象子类型:TypeA(具有json键"a" )和TypeB(具有json键"b" )

TypeA

示例:{"ct":12345,"id":"abc123, "a":{...}}

TypeA有15个孩子(我们称这些TypeA_ATypeA_P).这些对象的JSON表示形式类似于{"ct":12345,"id":"abc123, "a":{"aa":1 ...} ...}{"ct":12345,"id":"abc123, "a":{"ag":"Yo dawg I head you like JSON" ...} ...}

TypeB

示例:{"ct":12345,"id":"abc123, "b":{...} ...}

TypeB具有另一个抽象子类型(TypeB_A)和几个子类型(我们将这些TypeB_B称为TypeB_I).这些对象的JSON表示形式为{"ct":12345,"id":"abc123, "b":{"ba":{...} ...} ...}{"ct":12345,"id":"abc123, "b":{"bg":"Stayin alive" ...} ...}

可以将它们全部扔进一个怪物类型,并将每个子类型都当作一个内部对象,但是最后我会得到很多内部成员为null(类似于一棵有很多无处可通的树枝的树).结果,我将得到很多if (something==null),只是要确定我要处理的是哪种类型.

我已经看过TypeAdapterTypeAdapterFactory,但是我仍然不确定如何处理此问题,因为我必须查看传入的JSON的内容.

如何根据JSON对象的内容告诉GSON为我实例化哪种类型的对象?

谢谢.

解决方案

因此,RTTAF方法是朝着正确方向发展的一种方法,但是它希望我有一个字段表示我正在使用的子类型.在我的特定情况下,我具有这些子类型的子类型和子类型.这就是我最终要做的:

更新:创建了Github gist

注意:在下面的测试项目中,我使用GSON和Lombok进行注释.

类型工厂

public class CustomTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create (final Gson gson, final TypeToken<T> type) {
        if (type.getRawType () != SuperType.class)
            return null;

        final TypeAdapter<T> delegate = gson.getDelegateAdapter (this, type);

        return new TypeAdapter<T> () {
            @Override
            public void write (final JsonWriter jsonWriter, final T t) throws IOException {
                delegate.write (jsonWriter, t);
            }

            @Override
            public T read (final JsonReader jsonReader) throws IOException, JsonParseException {
                JsonElement tree = Streams.parse (jsonReader);
                JsonObject object = tree.getAsJsonObject ();

                if (object.has ("a"))
                    return (T) readTypeA (tree, object.getAsJsonObject ("a"));

                if (object.has ("b"))
                    return (T) readTypeB (tree, object.getAsJsonObject ("b"));

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid SuperType JSON.");
            }

            private TypeA readTypeA (final JsonElement tree, final JsonObject a) {
                if (a.has ("aa"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_A.class)).fromJsonTree (tree);

                if (a.has ("ab"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_B.class)).fromJsonTree (tree);

                if (a.has ("ac"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_C.class)).fromJsonTree (tree);

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid TypeA JSON.");
            }

            private TypeB readTypeB (final JsonElement tree, final JsonObject b) {
                if (b.has ("ba"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_A.class)).fromJsonTree (tree);

                if (b.has ("bb"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_B.class)).fromJsonTree (tree);

                if (b.has ("bc"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_C.class)).fromJsonTree (tree);

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid TypeB JSON.");
            }
        };
    }
}

SuperType.java

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class SuperType {
    @SerializedName ("ct")
    protected long creationTime;
    @SerializedName ("id")
    protected String id;
}

Type_A在其级别上没有其他数据,但是确实具有一些常见的行为(为简单起见,这里省略了方法,因为它们与解析无关).

TypeA.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeA extends SuperType {}

TypeA_A.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString(callSuper = true)
public class TypeA_A
  extends TypeA {

    @SerializedName ("a")
    protected AA aValue;

    @ToString
    private static class AA {
        @SerializedName ("aa")
        private String aaValue;
    }
}

其他Type_A子代与TypeA_A非常相似.

Type_B稍微复杂一点,因为它具有自己的数据行为(再次,为简单起见,将其省略):

TypeB.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB extends SuperType  {

// no member declared here

    protected static abstract class B {
        @SerializedName ("b1")
        protected String b1Value;
        @SerializedName ("b2")
        protected String b2Value;
    }
}

Type_BA.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB_A
  extends TypeB {

    @SerializedName ("b")
    protected BA bValue;

    @ToString
    private static class BA extends B {
        @SerializedName ("ba")
        private String baValue;
    }
}

TypeB_B.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB_B
  extends TypeB {

    @SerializedName ("b")
    protected BB bValue;

    @ToString
    private static class BB extends B {
        @SerializedName ("bb")
        private String bbValue;

        @SerializedName ("bb1")
        private String bb1Value;
    }
}

这里可能会有一些错别字,因为我不得不更改实际的类型名称和值,但是我将创建一个基本的Java代码示例并将其发布到Github.

感谢@Jesse Wilson和@Argyle为我指出正确方向的帮助.

I have a class hierarchy that I want to convert to and from GSON. I'm not sure how to approach this with GSON (I currently have a Factory class that looks at the JSONObject and based on presence or absence of keys it calls the right constructor, which in turn delegates some of its work to the super class). When I store these objects in the local SQLite DB, I use an integer to denote their type and the factory class uses this type to call the right constructor. I don't have this type in the JSON (which isn't mine).

How do I tell GSON based on the contents of the JSON object which type of object to instantiate for me?

In the examples below, treat ... inside the JSON brackets as there may or may not be more elements

Here's a breakdown of the class hierarchy:

There is a base abstract type: SuperType with a JSON representation {"ct":12345,"id":"abc123 ...}

There are 2 main abstract sub types: TypeA (has json key "a") and TypeB (has json key "b")

TypeA

Example: {"ct":12345,"id":"abc123, "a":{...}}

TypeA has 15 children (Let's call these TypeA_A to TypeA_P). The JSON representation of these objects would be something like {"ct":12345,"id":"abc123, "a":{"aa":1 ...} ...} or {"ct":12345,"id":"abc123, "a":{"ag":"Yo dawg I head you like JSON" ...} ...}

TypeB

Example: {"ct":12345,"id":"abc123, "b":{...} ...}

TypeB has another abstract subtype (TypeB_A) and few children (Let's call these TypeB_B to TypeB_I). The JSON representation of these objects would be {"ct":12345,"id":"abc123, "b":{"ba":{...} ...} ...} or {"ct":12345,"id":"abc123, "b":{"bg":"Stayin alive" ...} ...}

I could throw it all in one monster type and treat each of the sub types as an inner object, but I'll end up with a lot of inner members that are null (sort of like a tree with a lot of branches that lead to nowhere). As a result, I'll end up with a lot of if (something==null) just to determine which one of these types I'm dealing with.

I've looked at TypeAdapter and TypeAdapterFactory, but I'm still not sure how to approach this since I have to look at the content of the incoming JSON.

How do I tell GSON based on the contents of the JSON object which type of object to instantiate for me?

Thanks.

解决方案

So, the RTTAF approach was a push in the right direction, but it expects me to have a field that denotes which subtype I'm using. In my specific case, I have subtypes and subtypes of those subtypes. This is what I ended up doing:

UPDATE: Created Github gist

Note: In my test project (below), I'm using GSON and Lombok for annotations.

The Type Factory

public class CustomTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create (final Gson gson, final TypeToken<T> type) {
        if (type.getRawType () != SuperType.class)
            return null;

        final TypeAdapter<T> delegate = gson.getDelegateAdapter (this, type);

        return new TypeAdapter<T> () {
            @Override
            public void write (final JsonWriter jsonWriter, final T t) throws IOException {
                delegate.write (jsonWriter, t);
            }

            @Override
            public T read (final JsonReader jsonReader) throws IOException, JsonParseException {
                JsonElement tree = Streams.parse (jsonReader);
                JsonObject object = tree.getAsJsonObject ();

                if (object.has ("a"))
                    return (T) readTypeA (tree, object.getAsJsonObject ("a"));

                if (object.has ("b"))
                    return (T) readTypeB (tree, object.getAsJsonObject ("b"));

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid SuperType JSON.");
            }

            private TypeA readTypeA (final JsonElement tree, final JsonObject a) {
                if (a.has ("aa"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_A.class)).fromJsonTree (tree);

                if (a.has ("ab"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_B.class)).fromJsonTree (tree);

                if (a.has ("ac"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeA_C.class)).fromJsonTree (tree);

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid TypeA JSON.");
            }

            private TypeB readTypeB (final JsonElement tree, final JsonObject b) {
                if (b.has ("ba"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_A.class)).fromJsonTree (tree);

                if (b.has ("bb"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_B.class)).fromJsonTree (tree);

                if (b.has ("bc"))
                    return gson.getDelegateAdapter (CustomTypeAdapterFactory.this, TypeToken.get (TypeB_C.class)).fromJsonTree (tree);

                throw new JsonParseException ("Cannot deserialize " + type + ". It is not a valid TypeB JSON.");
            }
        };
    }
}

SuperType.java

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class SuperType {
    @SerializedName ("ct")
    protected long creationTime;
    @SerializedName ("id")
    protected String id;
}

Type_A has no additional data in its level, but does have some common behaviours (methods omitted here for simplicity and because they are irrelevant to parsing).

TypeA.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeA extends SuperType {}

TypeA_A.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString(callSuper = true)
public class TypeA_A
  extends TypeA {

    @SerializedName ("a")
    protected AA aValue;

    @ToString
    private static class AA {
        @SerializedName ("aa")
        private String aaValue;
    }
}

Other Type_A children are very similar to TypeA_A.

Type_B is slightly more complex as it has its own data and behaviours (again, omitted for simplicity):

TypeB.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB extends SuperType  {

// no member declared here

    protected static abstract class B {
        @SerializedName ("b1")
        protected String b1Value;
        @SerializedName ("b2")
        protected String b2Value;
    }
}

Type_BA.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB_A
  extends TypeB {

    @SerializedName ("b")
    protected BA bValue;

    @ToString
    private static class BA extends B {
        @SerializedName ("ba")
        private String baValue;
    }
}

TypeB_B.java

@Getter
@Setter
@EqualsAndHashCode (callSuper = true)
@ToString (callSuper = true)
public class TypeB_B
  extends TypeB {

    @SerializedName ("b")
    protected BB bValue;

    @ToString
    private static class BB extends B {
        @SerializedName ("bb")
        private String bbValue;

        @SerializedName ("bb1")
        private String bb1Value;
    }
}

There may be some typos here because I had to change the actual type names and values, but I will create a basic java code example and will post to Github.

Thanks to @Jesse Wilson and @Argyle for their help in pointing me in the right direction.

这篇关于如何使用gson解析类层次结构?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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