如何使用gson解析类层次结构?

时间:2014-02-08 02:09:01

标签: java json gson

我有一个类层次结构,我想要转换为GSON和从GSON转换。我不确定如何使用GSON来处理这个问题(我目前有一个Factory类来查看JSONObject,并根据它调用正确构造函数的键的存在与否,而后者又将其部分工作委托给超级)。当我将这些对象存储在本地SQLite DB中时,我使用一个整数来表示它们的类型,工厂类使用这种类型来调用正确的构造函数。我在JSON中没有 type (这不是我的)。

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

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

以下是类层次结构的细分:

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

有两个主要的抽象子类型:TypeA(有json键“a”)和TypeB(有json键“b”

类型A

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

TypeA有15个孩子(我们将这些TypeA_A称为TypeA_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要为我实例化哪种对象?

感谢。

2 个答案:

答案 0 :(得分:5)

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

更新Created 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帮助我指明正确的方向。

答案 1 :(得分:4)

有一个名为RuntimeTypeAdapterFactory的标准扩展名TypeAdapter,可以说明这一点。

test case提供了一些示例代码:

RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
    BillingInstrument.class)
    .registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(rta)
    .create();

CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
    gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
    "{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);