Gson使用存根序列化循环引用

时间:2016-12-18 22:55:26

标签: java json gson circular-dependency

我试图实现一些简单的Json序列化功能,但我很难应对Gson的大量复杂性。

所以基本上我有一堆实体类,它们通过大量循环引用相互引用。要将此结构序列化为JSON,我想跟踪已经序列化的对象。 Entity类都实现了一个名为Identified的接口,它有一个方法String getId()给出一个全局唯一的id。因此,在对一个根元素进行序列化时,我希望将所有遇到的id存储在Set中,并根据该集决定是完全序列化对象还是将该对象序列化为存根

"something": { "__stub": "true", "id": "..." }

在我看来,这不应该是一项太难的任务,但我还没有能够将某些东西放在一起。使用自定义JsonSerializer我无法以默认方式序列化对象(不是序列化为存根)。使用TypeAdapterFactory,我无法访问实际对象。

所以,任何有关如何实现这一目标的帮助都会非常好!

祝你好运

1 个答案:

答案 0 :(得分:0)

我不确定它是否可以轻松实现。据我所知,Gson提升了不变性,似乎缺乏自定义序列化上下文支持(至少我不知道是否可以尽可能使用自定义JsonSerializationContext)。因此,可能的解决方法之一可能如下:

IIdentifiable.java

请求对象的自定义ID的简单界面。

interface IIdentifiable<ID> {

    ID getId();

}

Entity.java

一个简单的实体,可以用两种方式保存另一个实体引用:

  • 直接依赖于&#34; next&#34;实体;
  • 对其他参考文献的引用集合。
final class Entity
        implements IIdentifiable<String> {

    @SerializedName(ID_PROPERTY_NAME)
    private final String id;

    private final Collection<Entity> entities = new ArrayList<>();
    private Entity next;

    private Entity(final String id) {
        this.id = id;
    }

    static Entity entity(final String id) {
        return new Entity(id);
    }

    @Override
    public String getId() {
        return id;
    }

    Entity setAll(final Entity... entities) {
        this.entities.clear();
        this.entities.addAll(asList(entities));
        return this;
    }

    Entity setNext(final Entity next) {
        this.next = next;
        return this;
    }

}

IdentitySerializingTypeAdapterFactory.java

我没有找到任何更简单的方法,而不是将其作为类型适配器工厂,不幸的是,此实现完全有状态无法重用

final class IdentitySerializingTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Collection<Object> traversedEntityIds = new HashSet<>();

    private IdentitySerializingTypeAdapterFactory() {
    }

    static TypeAdapterFactory identitySerializingTypeAdapterFactory() {
        return new IdentitySerializingTypeAdapterFactory();
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType());
        final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
        if ( isIdentifiable ) {
            return new TypeAdapter<T>() {
                @Override
                public void write(final JsonWriter out, final T value)
                        throws IOException {
                    final IIdentifiable<?> identifiable = (IIdentifiable<?>) value;
                    final Object id = identifiable.getId();
                    if ( !traversedEntityIds.contains(id) ) {
                        delegateAdapter.write(out, value);
                        traversedEntityIds.add(id);
                    } else {
                        out.beginObject();
                        out.name(REF_ID_PROPERTY_NAME);
                        writeSimpleValue(out, id);
                        out.endObject();
                    }
                }

                @Override
                public T read(final JsonReader in) {
                    throw new UnsupportedOperationException();
                }
            };
        }
        return delegateAdapter;
    }

}

类型适配器首先尝试检查是否已遍历给定实体。如果是,那么它会编写一个类似于你的特殊对象(行为可以通过策略模式重写,当然,但让它更简单)。如果不是,则获取默认类型适配器,然后将给定实体委托给该适配器,如果后一种类型适配器成功,则注册为遍历的实体。

其余

剩下的就是这里。

SystemNames.java

final class SystemNames {

    private SystemNames() {
    }

    private static final String SYSTEM_PREFIX = "__$";

    static final String ID_PROPERTY_NAME = SYSTEM_PREFIX + "id";
    static final String REF_ID_PROPERTY_NAME = SYSTEM_PREFIX + "refId";

}

GsonJsonWriters.java

final class GsonJsonWriters {

    private GsonJsonWriters() {
    }

    static void writeSimpleValue(final JsonWriter writer, final Object value)
            throws IOException {
        if ( value == null ) {
            writer.nullValue();
        } else if ( value instanceof Double ) {
            writer.value((double) value);
        } else if ( value instanceof Long ) {
            writer.value((long) value);
        } else if ( value instanceof String ) {
            writer.value((String) value);
        } else if ( value instanceof Boolean ) {
            writer.value((Boolean) value);
        } else if ( value instanceof Number ) {
            writer.value((Number) value);
        } else {
            throw new IllegalArgumentException("Cannot handle values of type " + value);
        }
    }

}

测试

在下面的测试中,有三个实体由FOOBARBAZ字符串标识符标识。所有这些都具有这样的循环依赖:

  • FOO - &gt; BARBAR - &gt; BAZBAZ - &gt; FOO使用next属性;
  • FOO - &gt; [BAR, BAZ]BAR - &gt; [FOO, BAZ]BAZ - &gt; [FOO, BAR]使用entities属性。

由于类型适配器工厂是有状态的,因此必须从头开始创建GsonBuilder,因此不会损坏&#34;#34;使用之间的状态。简单来说,一旦Gson实例被使用一次,就必须进行处置,因此下面的测试中有GsonBuilder个供应商。

public final class Q41213747Test {

    private static final Entity foo = entity("FOO");
    private static final Entity bar = entity("BAR");
    private static final Entity baz = entity("BAZ");

    static {
        foo.setAll(bar, baz).setNext(bar);
        bar.setAll(foo, baz).setNext(baz);
        baz.setAll(foo, bar).setNext(foo);
    }

    @Test
    public void testSerializeSameJson() {
        final String json1 = newSerializingGson().toJson(foo);
        final String json2 = newSerializingGson().toJson(foo);
        assertThat("Must be the same between the calls because the GSON instances are stateful", json1, is(json2));
    }

    @Test
    public void testSerializeNotSameJson() {
        final Gson gson = newSerializingGson();
        final String json1 = gson.toJson(foo);
        final String json2 = gson.toJson(foo);
        assertThat("Must not be the same between the calls because the GSON instance is stateful", json1, is(not(json2)));
    }

    @Test
    public void testOutput() {
        out.println(newSerializingGson().toJson(foo));
    }

    private static Gson newSerializingGson() {
        return newSerializingGson(GsonBuilder::new);
    }

    private static Gson newSerializingGson(final Supplier<GsonBuilder> defaultGsonBuilderSupplier) {
        return defaultGsonBuilderSupplier.get()
                .registerTypeAdapterFactory(identitySerializingTypeAdapterFactory())
                .create();
    }

}
{
    "__$id": "FOO",
    "entities": [
        {
            "__$id": "BAR",
            "entities": [
                {
                    "__$refId": "FOO"
                },
                {
                    "__$id": "BAZ",
                    "entities": [
                        {
                            "__$refId": "FOO"
                        },
                        {
                            "__$refId": "BAR"
                        }
                    ],
                    "next": {
                        "__$refId": "FOO"
                    }
                }
            ],
            "next": {
                "__$refId": "BAZ"
            }
        },
        {
            "__$refId": "BAZ"
        }
    ],
    "next": {
        "__$refId": "BAR"
    }
}

这些东西的反序列化看起来非常复杂。至少使用GSON设施。

您是否考虑重新考虑JSON模型以避免JSON输出中的循环依赖?也许将您的对象分解为单个地图,例如Map<ID, Object>,并使参考文件瞬态或@Expose - 注释可能更容易使用?它也会简化反序列化。