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

我正在尝试实现一些简单的Json序列化function,但我很难应对Gson的大量复杂性。

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

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

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

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

最好的祝福

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

IIdentifiable.java

一个简单的接口,用于请求对象的自定义ID。

 interface IIdentifiable { ID getId(); } 

Entity.java

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

  • 直接依赖于“下一个”实体;
  • 其他参考文献的引用集合。
 final class Entity implements IIdentifiable { @SerializedName(ID_PROPERTY_NAME) private final String id; private final Collection 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 traversedEntityIds = new HashSet<>(); private IdentitySerializingTypeAdapterFactory() { } static TypeAdapterFactory identitySerializingTypeAdapterFactory() { return new IdentitySerializingTypeAdapterFactory(); } @Override public  TypeAdapter create(final Gson gson, final TypeToken typeToken) { final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType()); final TypeAdapter delegateAdapter = gson.getDelegateAdapter(this, typeToken); if ( isIdentifiable ) { return new TypeAdapter() { @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 – > BARBAR – > BAZBAZ – > FOO使用next房产;
  • FOO – > [BAR, BAZ]BAR – > [FOO, BAZ]BAZ – > [FOO, BAR]使用entities属性。

由于类型适配器工厂是有状态的,因此即使是GsonBuilder必须从头开始创建,因此在使用之间不会出现“损坏”状态。 简单来说,一旦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 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并使参考瞬态或@Expose -annotated可以更容易使用? 它也会简化反序列化。