我不确定这是否容易实现。据我所知,Gson 提倡不变性,并且似乎缺乏自定义序列化上下文支持(至少我不知道是否可以使用自定义JsonSerializationContext
尽可能)。因此,可能的解决方法之一可能如下:
可以识别的.java
用于请求对象的自定义 ID 的简单接口。
interface IIdentifiable<ID> {
ID getId();
}
实体.java
一个简单实体,可以通过两种方式保存另一个实体引用:
- 对“下一个”实体的直接依赖;
- 对其他参考文献的引用的集合。
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
除了将其设为类型适配器工厂之外,我没有找到任何更简单的方法,不幸的是,这个实现是完全有状态的 and 不能重复使用.
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;
}
}
类型适配器首先尝试检查给定实体是否已被遍历。如果是,那么它正在编写一个与您的对象类似的特殊对象(当然,可以通过策略模式重写该行为,但让它更简单)。如果否,则获取默认类型适配器,然后将给定实体委托给该适配器,如果后一个类型适配器成功,则将其注册为遍历实体。
The rest
剩下的就是这里了。
系统名称.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);
}
}
}
Testing
在下面的测试中,存在由以下标识的三个实体FOO
, BAR
, and BAZ
字符串标识符。它们都具有如下循环依赖关系:
-
FOO
-> BAR
, BAR
-> BAZ
, BAZ
-> FOO
使用next
财产;
-
FOO
-> [BAR, BAZ]
, BAR
-> [FOO, BAZ]
, BAZ
-> [FOO, BAR]
使用entities
财产。
由于类型适配器工厂是有状态的,甚至GsonBuilder
必须从头开始创建,因此在使用之间不会出现“损坏”状态。简单来说,Gson实例一旦被使用一次,就必须被dispose,所以有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
- 带注释的可以更容易使用吗?它也会简化反序列化。