使用 Gson-extras 使用瞬态变量反序列化多态 JSON
Deserializing polymorphic JSON with transient variables using Gson-extras
我有一些多态 java class,我是 serializing/deserializing。我正在使用 Gson-extras 中的 RuntimeTypeAdapterFactory 来确保 classes 序列化和反序列化正确,"type" 字段设置为派生的 class 的名称。一切正常。
我有一些其他的 classes,我是 serializing/deserializing,其中有一些应用了 transient 修饰符的变量。我正在使用同样来自 Gson-extras 的 PostConstructAdapterFactory 向我的 classes 添加一个 PostConstruct 方法,以便在反序列化完成后和 [=] 中的任何函数之前可以重新分配瞬态变量的值41=]es 被调用。这也很好用。
我面临的挑战是我有另一组 class es,它们是多态的并且还具有瞬态变量,我希望在反序列化后执行 PostConstruct 方法。我所有同时使用 RuntimeTypeAdapterFactory 和 PostConstructAdapterFactory 的尝试都失败了。我可以同时注册两者,但是当我执行 PostConstructAdapterFactory 行为时,它的行为似乎覆盖了 RuntimeTypeAdapterFactory 行为,导致我的 classes 不再存储多态 classes 所需的 "type" 字段。
看来我可以选择其中之一,但不能同时选择两者。我什至考虑编写一个具有两个 Adapter Factories 功能的混合 AdapterFactory class,但这也没有成功。
我觉得我可能遗漏了一些关于这些设计方式的明显信息。我的 classes 可以同时使用这两种功能吗?下面是一个简单的例子来证明我在说什么。
public class BaseClass {
private static final RuntimeTypeAdapterFactory<BaseClass> ADAPTER = RuntimeTypeAdapterFactory.of(BaseClass.class);
private static final HashSet<Class<?>> REGISTERED_CLASSES= new HashSet<Class<?>>();
static { GsonUtils.registerType(ADAPTER); }
private synchronized void registerClass() {
if (!REGISTERED_CLASSES.contains(this.getClass())) {
REGISTERED_CLASSES.add(this.getClass());
ADAPTER.registerSubtype(this.getClass());
}
}
public BaseClass() {
registerClass();
}
}
public class DerivedClass extends BaseClass {
public DerivedClass(Integer number) {
super();
this.number = number;
Init();
}
protected Integer number;
protected transient Integer doubled;
protected transient Integer tripled;
protected transient Integer halved;
protected transient Integer squared;
protected transient Integer cubed;
public void Init() {
halved = number / 2;
doubled = number * 2;
tripled = number * 3;
squared = number * number;
cubed = number * number * number;
}
@PostConstruct
private void postConstruct() {
Init();
}
}
public class GsonUtils {
private static final GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory());
public static void registerType(RuntimeTypeAdapterFactory<?> adapter) {
gsonBuilder.registerTypeAdapterFactory(adapter);
}
public static Gson getGson() {
return gsonBuilder.create();
}
}
Gson gson = GsonUtils.getGson();
String serialized = gson.toJson(new DerivedClass(6), BaseClass.class);
DerivedClass dc = gson.fromJson(serialized, DerivedClass.class);
更新:感谢@michał-ziober 的回答。按你说的做确实有效。它确实回答了这个问题,但它仍然没有完全解决我的问题。让我稍微修改一下问题。
我的代码的简化版本可能有点太简单了。当我尝试将 serialize/deserialize DerivedClass 作为另一个 class 上的 属性 时,我原来的问题被突出显示。如下:
public class WrapperClass {
protected BaseClass dc = new DerivedClass(6);
}
Gson gson = GsonUtils.getGson();
String serialized = gson.toJson(new WrapperClass());
WrapperClass wc = gson.fromJson(serialized, WrapperClass.class);
在这种情况下,只要我的 DerivedClass 没有定义 PostConstruct 方法,DerivedClass 的 serialization/deserialization 就可以正常工作。但如果是这样,"type" 属性 不会写入文件。
重申一下,如果我直接 serialize/deserialize DerivedClass,一切都很好,但是如果它在 WrapperClass 中,我 serialize/deserialize WrapperClass 并且如果我定义了 PostConstruct 方法,它就不起作用。
使用类型适配器一切都很好,您可以同时使用它们。在您的情况下,问题在于执行代码的顺序。
GsonUtils.getGson()
- 使用适配器创建 Gson
对象。
gson.toJson(new DerivedClass(6), BaseClass.class)
- 您创建 DerivedClass
实例,该实例从 BaseClass
执行超级构造函数,该实例在适配器中注册给定 class(原文如此!)。
试试这个代码:
DerivedClass src = new DerivedClass(6);
Gson gson1 = GsonUtils.getGson();
String serialized = gson1.toJson(src, BaseClass.class);
System.out.println(serialized);
DerivedClass dc = gson1.fromJson(serialized, DerivedClass.class);
它将按预期工作。创建 DerivedClass
有一个副作用——这是一个很好的例子,为什么 class 应该相互分离。 BaseClass
.
中不应有任何 Gson
特定的 class
最好的世界BaseClass
应该只包含公共属性和一些基本逻辑:
class BaseClass {
// getters, setters, business logic.
}
所有Gson
配置应该放在GsonUtils
class:
class GsonUtils {
public static Gson getGson() {
return new GsonBuilder()
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(BaseClass.class)
.registerSubtype(DerivedClass.class))
.registerTypeAdapterFactory(new PostConstructAdapterFactory())
.create();
}
}
如果您不想手动指定所有classes,您需要在运行时扫描环境。看看:At runtime, find all classes in a Java application that extend a base class.
问题更新后编辑
看起来 TypeAdapterFactory
链接并不像我预期的那么简单。这取决于给定的 TypeAdapterFactory
实现,它是 returns null
还是 returns 新对象与一些委托给其他工厂。
我找到了一个解决方法,但在实际项目中可能不容易使用:
- 我创建了两个
Gson
对象:一个用于序列化,一个用于反序列化。仅针对反序列化过程,我们注册 PostConstructAdapterFactory
.
- 我们在
BaseClass
. 中添加了带有 @PostConstruct
注释的方法
示例:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.typeadapters.PostConstructAdapterFactory;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
import javax.annotation.PostConstruct;
public class GsonApp {
public static void main(String[] args) {
RuntimeTypeAdapterFactory<BaseClass> typeFactory = RuntimeTypeAdapterFactory.of(BaseClass.class)
.registerSubtype(DerivedClass.class);
Gson serializer = new GsonBuilder()
.registerTypeAdapterFactory(typeFactory)
.create();
Gson deserializer = new GsonBuilder()
.registerTypeAdapterFactory(typeFactory)
.registerTypeAdapterFactory(new PostConstructAdapterFactory())
.create();
WrapperClass wrapper = new WrapperClass();
wrapper.setDc(new DerivedClass(8));
String json = serializer.toJson(wrapper);
System.out.println(json);
System.out.println(deserializer.fromJson(json, WrapperClass.class));
}
}
class WrapperClass {
protected BaseClass dc;
public BaseClass getDc() {
return dc;
}
public void setDc(BaseClass dc) {
this.dc = dc;
}
@Override
public String toString() {
return "WrapperClass{" +
"dc=" + dc +
'}';
}
}
class BaseClass {
@PostConstruct
protected void postConstruct() {
}
}
class DerivedClass extends BaseClass {
public DerivedClass(Integer number) {
super();
this.number = number;
Init();
}
protected Integer number;
protected transient Integer doubled;
protected transient Integer tripled;
protected transient Integer halved;
protected transient Integer squared;
protected transient Integer cubed;
public void Init() {
halved = number / 2;
doubled = number * 2;
tripled = number * 3;
squared = number * number;
cubed = number * number * number;
}
@PostConstruct
protected void postConstruct() {
Init();
}
@Override
public String toString() {
return "DerivedClass{" +
"number=" + number +
", doubled=" + doubled +
", tripled=" + tripled +
", halved=" + halved +
", squared=" + squared +
", cubed=" + cubed +
"} ";
}
}
以上代码打印:
{"dc":{"type":"DerivedClass","number":8}}
WrapperClass{dc=DerivedClass{number=8, doubled=16, tripled=24, halved=4, squared=64, cubed=512} }
我有一些多态 java class,我是 serializing/deserializing。我正在使用 Gson-extras 中的 RuntimeTypeAdapterFactory 来确保 classes 序列化和反序列化正确,"type" 字段设置为派生的 class 的名称。一切正常。
我有一些其他的 classes,我是 serializing/deserializing,其中有一些应用了 transient 修饰符的变量。我正在使用同样来自 Gson-extras 的 PostConstructAdapterFactory 向我的 classes 添加一个 PostConstruct 方法,以便在反序列化完成后和 [=] 中的任何函数之前可以重新分配瞬态变量的值41=]es 被调用。这也很好用。
我面临的挑战是我有另一组 class es,它们是多态的并且还具有瞬态变量,我希望在反序列化后执行 PostConstruct 方法。我所有同时使用 RuntimeTypeAdapterFactory 和 PostConstructAdapterFactory 的尝试都失败了。我可以同时注册两者,但是当我执行 PostConstructAdapterFactory 行为时,它的行为似乎覆盖了 RuntimeTypeAdapterFactory 行为,导致我的 classes 不再存储多态 classes 所需的 "type" 字段。
看来我可以选择其中之一,但不能同时选择两者。我什至考虑编写一个具有两个 Adapter Factories 功能的混合 AdapterFactory class,但这也没有成功。
我觉得我可能遗漏了一些关于这些设计方式的明显信息。我的 classes 可以同时使用这两种功能吗?下面是一个简单的例子来证明我在说什么。
public class BaseClass {
private static final RuntimeTypeAdapterFactory<BaseClass> ADAPTER = RuntimeTypeAdapterFactory.of(BaseClass.class);
private static final HashSet<Class<?>> REGISTERED_CLASSES= new HashSet<Class<?>>();
static { GsonUtils.registerType(ADAPTER); }
private synchronized void registerClass() {
if (!REGISTERED_CLASSES.contains(this.getClass())) {
REGISTERED_CLASSES.add(this.getClass());
ADAPTER.registerSubtype(this.getClass());
}
}
public BaseClass() {
registerClass();
}
}
public class DerivedClass extends BaseClass {
public DerivedClass(Integer number) {
super();
this.number = number;
Init();
}
protected Integer number;
protected transient Integer doubled;
protected transient Integer tripled;
protected transient Integer halved;
protected transient Integer squared;
protected transient Integer cubed;
public void Init() {
halved = number / 2;
doubled = number * 2;
tripled = number * 3;
squared = number * number;
cubed = number * number * number;
}
@PostConstruct
private void postConstruct() {
Init();
}
}
public class GsonUtils {
private static final GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory());
public static void registerType(RuntimeTypeAdapterFactory<?> adapter) {
gsonBuilder.registerTypeAdapterFactory(adapter);
}
public static Gson getGson() {
return gsonBuilder.create();
}
}
Gson gson = GsonUtils.getGson();
String serialized = gson.toJson(new DerivedClass(6), BaseClass.class);
DerivedClass dc = gson.fromJson(serialized, DerivedClass.class);
更新:感谢@michał-ziober 的回答。按你说的做确实有效。它确实回答了这个问题,但它仍然没有完全解决我的问题。让我稍微修改一下问题。
我的代码的简化版本可能有点太简单了。当我尝试将 serialize/deserialize DerivedClass 作为另一个 class 上的 属性 时,我原来的问题被突出显示。如下:
public class WrapperClass {
protected BaseClass dc = new DerivedClass(6);
}
Gson gson = GsonUtils.getGson();
String serialized = gson.toJson(new WrapperClass());
WrapperClass wc = gson.fromJson(serialized, WrapperClass.class);
在这种情况下,只要我的 DerivedClass 没有定义 PostConstruct 方法,DerivedClass 的 serialization/deserialization 就可以正常工作。但如果是这样,"type" 属性 不会写入文件。
重申一下,如果我直接 serialize/deserialize DerivedClass,一切都很好,但是如果它在 WrapperClass 中,我 serialize/deserialize WrapperClass 并且如果我定义了 PostConstruct 方法,它就不起作用。
使用类型适配器一切都很好,您可以同时使用它们。在您的情况下,问题在于执行代码的顺序。
GsonUtils.getGson()
- 使用适配器创建Gson
对象。gson.toJson(new DerivedClass(6), BaseClass.class)
- 您创建DerivedClass
实例,该实例从BaseClass
执行超级构造函数,该实例在适配器中注册给定 class(原文如此!)。
试试这个代码:
DerivedClass src = new DerivedClass(6);
Gson gson1 = GsonUtils.getGson();
String serialized = gson1.toJson(src, BaseClass.class);
System.out.println(serialized);
DerivedClass dc = gson1.fromJson(serialized, DerivedClass.class);
它将按预期工作。创建 DerivedClass
有一个副作用——这是一个很好的例子,为什么 class 应该相互分离。 BaseClass
.
Gson
特定的 class
最好的世界BaseClass
应该只包含公共属性和一些基本逻辑:
class BaseClass {
// getters, setters, business logic.
}
所有Gson
配置应该放在GsonUtils
class:
class GsonUtils {
public static Gson getGson() {
return new GsonBuilder()
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(BaseClass.class)
.registerSubtype(DerivedClass.class))
.registerTypeAdapterFactory(new PostConstructAdapterFactory())
.create();
}
}
如果您不想手动指定所有classes,您需要在运行时扫描环境。看看:At runtime, find all classes in a Java application that extend a base class.
问题更新后编辑
看起来 TypeAdapterFactory
链接并不像我预期的那么简单。这取决于给定的 TypeAdapterFactory
实现,它是 returns null
还是 returns 新对象与一些委托给其他工厂。
我找到了一个解决方法,但在实际项目中可能不容易使用:
- 我创建了两个
Gson
对象:一个用于序列化,一个用于反序列化。仅针对反序列化过程,我们注册PostConstructAdapterFactory
. - 我们在
BaseClass
. 中添加了带有
@PostConstruct
注释的方法
示例:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.typeadapters.PostConstructAdapterFactory;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
import javax.annotation.PostConstruct;
public class GsonApp {
public static void main(String[] args) {
RuntimeTypeAdapterFactory<BaseClass> typeFactory = RuntimeTypeAdapterFactory.of(BaseClass.class)
.registerSubtype(DerivedClass.class);
Gson serializer = new GsonBuilder()
.registerTypeAdapterFactory(typeFactory)
.create();
Gson deserializer = new GsonBuilder()
.registerTypeAdapterFactory(typeFactory)
.registerTypeAdapterFactory(new PostConstructAdapterFactory())
.create();
WrapperClass wrapper = new WrapperClass();
wrapper.setDc(new DerivedClass(8));
String json = serializer.toJson(wrapper);
System.out.println(json);
System.out.println(deserializer.fromJson(json, WrapperClass.class));
}
}
class WrapperClass {
protected BaseClass dc;
public BaseClass getDc() {
return dc;
}
public void setDc(BaseClass dc) {
this.dc = dc;
}
@Override
public String toString() {
return "WrapperClass{" +
"dc=" + dc +
'}';
}
}
class BaseClass {
@PostConstruct
protected void postConstruct() {
}
}
class DerivedClass extends BaseClass {
public DerivedClass(Integer number) {
super();
this.number = number;
Init();
}
protected Integer number;
protected transient Integer doubled;
protected transient Integer tripled;
protected transient Integer halved;
protected transient Integer squared;
protected transient Integer cubed;
public void Init() {
halved = number / 2;
doubled = number * 2;
tripled = number * 3;
squared = number * number;
cubed = number * number * number;
}
@PostConstruct
protected void postConstruct() {
Init();
}
@Override
public String toString() {
return "DerivedClass{" +
"number=" + number +
", doubled=" + doubled +
", tripled=" + tripled +
", halved=" + halved +
", squared=" + squared +
", cubed=" + cubed +
"} ";
}
}
以上代码打印:
{"dc":{"type":"DerivedClass","number":8}}
WrapperClass{dc=DerivedClass{number=8, doubled=16, tripled=24, halved=4, squared=64, cubed=512} }