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

2024-01-31

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

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

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

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

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

此致


我不确定这是否容易实现。据我所知,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- 带注释的可以更容易使用吗?它也会简化反序列化。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Gson 使用存根序列化循环引用 的相关文章

随机推荐

  • Android Studio 中的类文件夹在哪里添加?

    我想在与活动不同的文件夹中设置课程 当前文件夹结构 gt APP gt Manifests gt Java gt com test testing gt classes auth java home activity gt libs aut
  • 同时运行 HAXM AVD 模拟器限制

    为了详细说明标题 我正在为我的自动化构建 测试流程运行许多同步 AVD 想想一个项目的并发构建 或一次构建多个项目 人们在同时运行多个 HAXM 加速 AVD 时遇到的大多数问题都与内存相关 这不是我的问题 我认为 我有一台非常强大的 i7
  • 在 SQL Server 中使用日期时间值自动填充表

    我有一张桌子叫Appointment我想在其中显示约会空档 60min intervals from 10am to 3pm for Weekdays till August 下面是我的表结构 AppointmentID Date Star
  • 如何在多线程中使用等待和通知协议

    具体来说 有人可以告诉我这段代码有什么问题吗 它应该启动线程 因此应该打印 Entering thread 5次 然后等待 直到调用notifyAll 但是 它随机打印 正在输入 和 完成 并且仍然继续等待其他人 public class
  • 离线安装 Android 支持存储库

    我正在尝试在 Linux 上设置 Android Studio Android SDK 我能够设置 Android Studio 和 SDK 工具以及除 Android 支持存储库 之外的其他组件 下载一直失败 所以我下载了android
  • sap hana - 选择顶部表达式[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我的存储过程有问题 该过程获取所需行数作为参数 但以下内容在 HANA 中不起作用 SELECT TOP NUM OF ROWS
  • 在 iOS 模拟器中按两次 Home 按钮不起作用

    如果我尝试在与 Xcode 8 3 捆绑的 iOS Simulator 10 3 中使用键盘手势显示多任务菜单 它无法响应 然而 按下单个按钮和菜单中的选项仍然有效 我该怎么做才能让它再次工作 If you disconnect the h
  • ExoPlayer - 如何播放本地mp3文件

    我正在尝试使用 ExoPlayer 而不是 MediaPlayer 因为 MediaPlayer 返回错误的 getCurrentPosition 是一个常见错误 我需要一个替代品 但我无法在任何地方找到如何通过与 MediaPlayer
  • 创建具有默认值的合并两列的表

    是否可以创建一个包含组合两个列值的列的表 像这样的东西 创建表test1 number1 小数 6 2 number2 小数 6 2 总小数 6 2 DEFAULT number1 number2 是的 11G 它被称为 虚拟 列 语法是
  • 轻量级进程和线程有什么区别?

    我找到了问题的答案here http wiki answers com Q What is the difference between LWP and threads 但我不明白答案中的一些想法 例如 据说轻量级进程与其他进程共享其逻辑地
  • PG::ProgramLimitExceeded: 错误: 索引行需要 13904 字节,最大大小为 8191

    我有一个领域 t text po description null false 我试图在此字段中保存 200 300 行文本并收到溢出错误 PG ProgramLimitExceeded 错误 索引行需要 13904 字节 最大大小为 81
  • 动态生成 JavaScript 的最佳实践

    将动态内容添加到 JavaScript 尤其是配置设置 的最佳方法是什么 几种可能性是 将内容放置在动态生成的文件 例如 JSP PHP 等 中 而不是 JavaScript 文件中 可能使用对象文字 通过 ajax 从服务器请求 JSON
  • 打破单子序列

    是否有可能打破单子序列 例如 如果我想根据序列中间计算的某些条件提前打破序列 比如说 在 do 符号中 我绑定了一个值 并根据该值我想要完成序列或停止序列 有没有类似 pass 的功能 Thanks 直接使用if 你可以直接这样做Ingo
  • 仅在 enumerate 和 itemize 环境中更改 \parskip

    有什么办法可以改变我 parskip在某些环境中具有不同的值 即enumerate and itemize 我想在段落之间留有空格 setlength parskip 1em plus 1pt minus 1pt 但不在 itemize 或
  • [IOS]仅支持视网膜设备

    我真的很困惑 如果我只捆绑 2x 图像 并且我只有视网膜屏幕的布局 IOS会再次扩容吗 比如我传点 800 600 给opengl es IOS会改成 1600 1200 吗 因为我不想支持非视网膜设备 所以 800 600 已经是正确的位
  • 从 R 运行 linux 命令

    我有一堆随机文件 我将对每个文件运行 LINUX file 命令 Linux 屏幕将如下所示 m7 file date file csv date file csv ASCII text with CRLF line terminators
  • 使用 CoreGraphics 查找二次贝塞尔曲线的最小值/最大值

    我正在使用 CoreGraphics 绘制二次贝塞尔曲线 但想要计算曲线的最小 最大值 我不是数学背景 所以这变得有点麻烦 有谁有关于如何解决这个问题的文章或想法 对于二次贝塞尔曲线来说 这实际上非常简单 将三个控制点定义为P0 x0 y0
  • Nginx 重写查询参数并进行多次替换

    您好 我们使用 nginx 由于系统发生变化 我们必须临时使用查询参数对某些 URL 进行 301 处理 我进行了很多搜索但没有找到解决方案 我们想 将查询参数列表替换为新值 进行多次替换 所以我们的 URI 应该重写为 page manu
  • C# RSA 无填充

    我正忙着尝试移植如下所示的 Java 代码 Cipher rsa Cipher getInstance RSA ECB nopadding rsa init Cipher DECRYPT MODE RSAPrivateKey decrypt
  • Gson 使用存根序列化循环引用

    我正在尝试实现一些简单的 Json 序列化功能 但我很难应对 Gson 的巨大复杂性 所以基本上我有一堆实体类 它们通过大量循环引用相互引用 为了将此结构序列化为 JSON 我想跟踪已序列化的对象 实体类都实现了一个名为Identified