簡體   English   中英

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

[英]Gson Serialize Circular References Using Stubs

我正在嘗試實現一些簡單的Json序列化功能,但是我很難應付Gson的巨大復雜性。

因此,基本上,我有一堆Entity類,它們通過大量循環引用相互引用。 要將這個結構序列化為JSON,我想跟蹤已經序列化的對象。 所有的Entity類都實現了一個名為Identified的接口,該接口具有一個提供全局唯一ID的方法String getId() 因此,在序列化一個根元素的過程中,我想將所有遇到的id存儲在Set並根據該set決定是完全序列化對象還是將該對象序列化為存根

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

在我看來,這應該不是一件容易的事,但我無法將某些東西放在一起。 使用自定義JsonSerializer我無法以默認方式序列化一個對象(該序列不會作為存根序列化)。 使用TypeAdapterFactory ,我無法訪問實際的對象。

因此,任何有關如何實現這一目標的幫助都將非常好!

最好的祝福

我不確定是否可以輕松實現。 據我所知,Gson促進了不變性,並且似乎缺乏自定義序列化上下文支持(至少我不知道是否有可能在任何可能的地方使用自定義JsonSerializationContext )。 因此,可能的解決方法之一可能是:

IIdentifiable.java

一個簡單的接口,用於請求對象的自定義ID。

interface IIdentifiable<ID> {

    ID getId();

}

Entity.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

除了使它成為類型適配器工廠之外,我沒有找到任何更簡單的方法,而且不幸的是,此實現是完全有狀態的無法重用

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 > BARBAR > BAZBAZ > FOO使用next屬性;
  • FOO > [BAR, BAZ]BAR > [FOO, BAZ]BAZ > [FOO, BAR]使用entities屬性。

由於類型適配器工廠是有狀態的,因此甚至GsonBuilder必須從頭開始創建,因此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<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 -annotated可能更容易使用? 這也將簡化反序列化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM