使用 Gson 序列化具有瞬态字段的对象

Posted

技术标签:

【中文标题】使用 Gson 序列化具有瞬态字段的对象【英文标题】:Using Gson to serialize object with transient fields 【发布时间】:2022-01-15 23:01:42 【问题描述】:

我正在尝试将一个对象转换为具有 InetSocketAddress 实例作为属性的 JSON。 InetSocketAdress 中的 holder 属性被标记为瞬态,因此在序列化为 JSON 时被 Gson 忽略。

我无法删除瞬态关键字,因为 InetSocketAddress 类内置于 java.net 中。

我还尝试将excludeFieldsWithModifiers() 用于transientstatic 修饰符:

final Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
String json = gson.toJson(registration, Registration.class);

这会产生以下错误:

java.lang.reflect.InaccessibleObjectException: Unable to make field private static final long java.net.InetSocketAddress.serialVersionUID accessible: module java.base does not "opens java.net" to unnamed module @3e849b9e
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
    at com.google.gson.internal.reflect.UnsafeReflectionAccessor.makeAccessible(UnsafeReflectionAccessor.java:44)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:159)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
    at com.google.gson.Gson.getAdapter(Gson.java:489)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
    at com.google.gson.Gson.getAdapter(Gson.java:489)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
    at com.google.gson.Gson.getAdapter(Gson.java:489)
    at com.google.gson.Gson.toJson(Gson.java:727)
    at com.google.gson.Gson.toJson(Gson.java:714)
    at com.google.gson.Gson.toJson(Gson.java:669)
    at org.eclipse.leshan.server.demo.websocket.WebSocketClient$1.registered(WebSocketClient.java:61)
    at org.eclipse.leshan.server.registration.RegistrationServiceImpl.fireRegistered(RegistrationServiceImpl.java:74)
    at org.eclipse.leshan.server.registration.RegistrationHandler$1.run(RegistrationHandler.java:86)
    at org.eclipse.leshan.core.response.SendableResponse.sent(SendableResponse.java:47)
    at org.eclipse.leshan.server.californium.registration.RegisterResource.handleRegister(RegisterResource.java:195)
    at org.eclipse.leshan.server.californium.registration.RegisterResource.handlePOST(RegisterResource.java:104)
    at org.eclipse.californium.core.CoapResource.handleRequest(CoapResource.java:219)
    at org.eclipse.leshan.core.californium.LwM2mCoapResource.handleRequest(LwM2mCoapResource.java:51)
    at org.eclipse.californium.core.server.ServerMessageDeliverer.deliverRequest(ServerMessageDeliverer.java:106)
    at org.eclipse.californium.core.network.stack.BaseCoapStack$StackTopAdapter.receiveRequest(BaseCoapStack.java:207)
    at org.eclipse.californium.core.network.stack.AbstractLayer.receiveRequest(AbstractLayer.java:84)
    at org.eclipse.californium.core.network.stack.AbstractLayer.receiveRequest(AbstractLayer.java:84)
    at org.eclipse.californium.core.network.stack.BlockwiseLayer.receiveRequest(BlockwiseLayer.java:488)
    at org.eclipse.californium.core.network.stack.ReliabilityLayer.receiveRequest(ReliabilityLayer.java:296)
    at org.eclipse.californium.core.network.stack.AbstractLayer.receiveRequest(AbstractLayer.java:84)
    at org.eclipse.californium.core.network.stack.BaseCoapStack.receiveRequest(BaseCoapStack.java:140)
    at org.eclipse.californium.core.network.CoapEndpoint$1.receiveRequest(CoapEndpoint.java:302)
    at org.eclipse.californium.core.network.UdpMatcher$2.run(UdpMatcher.java:289)
    at org.eclipse.californium.elements.util.SerialExecutor$1.run(SerialExecutor.java:290)
    at org.eclipse.californium.core.network.CoapEndpoint$6.run(CoapEndpoint.java:1311)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

我该如何解决这个问题?

【问题讨论】:

InetSocketAddress 由地址和端口组成。您可能不得不序列化地址的字符串版本,并在反序列化后使用它来创建InetSocketAddress 您可以尝试添加此参数吗? --add-opens java.base/java.net=ALL-UNNAMED 【参考方案1】:

真正的问题是gson库内部使用反射策略来访问私有字段。在 java 9 及以上版本中,不允许使用反射访问私有。

一个可能的解决方案是,检查您使用的 java 版本。 如果是 Java 9 或更高版本,则尝试降级到 Java 8。

【讨论】:

【参考方案2】:

这里的根本问题是您(隐式地)将基于反射的 Gson 类型适配器用于第三方类(在本例中为 JDK 类)的私有字段。通过这种方式,您可以让自己依赖于该类的实现细节,这些细节可能随时发生变化,或者如这里所示可能变得无法访问。

这里“正确”且面向未来的解决方案是为 InetSocketAdress 类编写自定义 TypeAdapter

【讨论】:

以上是关于使用 Gson 序列化具有瞬态字段的对象的主要内容,如果未能解决你的问题,请参考以下文章

如何使用GSON配置哪些POJO字段序列化为JSON?

Spring Data 和 Redis - 序列化瞬态字段

根据GSON中的值从序列化中排除某些字段

GSON使用笔记 -- 序列化时排除字段的几种方式

使用 gson 反序列化反序列化对象的内部对象

Map 子类的 Gson 序列化也具有显式定义的属性