Java Gson 序列化多态对象列表
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19588020/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Gson serialize a list of polymorphic objects
提问by l46kok
I'm trying to serialize/deserialize an object, that involves polymorphism, into JSON using Gson.
我正在尝试使用 Gson 将涉及多态的对象序列化/反序列化为 JSON。
This is my code for serializing:
这是我的序列化代码:
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));
Here's the result:
结果如下:
{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch"}]}
The serialization mostly works, except its missing the contents of inherited members (In particular obix:BatchIn
and obixBatchout
strings are missing).
Here's my base class:
序列化大多的作品,除了它缺少继承成员的内容(尤其是obix:BatchIn
和obixBatchout
字符串丢失)。这是我的基类:
public class ObixBaseObj {
protected String obix;
private String display;
private String displayName;
private ArrayList<ObixBaseObj> children;
public ObixBaseObj()
{
obix = "obj";
}
public void setName(String name) {
this.name = name;
}
...
}
Here's what my inherited class (ObixOp) looks like:
这是我继承的类 (ObixOp) 的样子:
public class ObixOp extends ObixBaseObj {
private String in;
private String out;
public ObixOp() {
obix = "op";
}
public ObixOp(String in, String out) {
obix = "op";
this.in = in;
this.out = out;
}
public String getIn() {
return in;
}
public void setIn(String in) {
this.in = in;
}
public String getOut() {
return out;
}
public void setOut(String out) {
this.out = out;
}
}
I realize I could use an adapter for this, but the problem is that I'm serializing a collection of base class type ObixBaseObj
. There are about 25 classes that inherits from this. How can I make this work elegantly?
我意识到我可以为此使用适配器,但问题是我正在序列化基类类型的集合ObixBaseObj
。大约有 25 个类继承自此。我怎样才能优雅地完成这项工作?
采纳答案by giampaolo
I think that a custom serializer/deserializer is the only way to proceed and I tried to propose you the most compact way to realize it I have found. I apologize for not using your classes, but the idea is the same (I just wanted at least 1 base class and 2 extended classes).
我认为自定义序列化器/反序列化器是唯一的方法,我试图向您提出我发现的最紧凑的实现方式。我很抱歉没有使用你的类,但想法是一样的(我只想要至少 1 个基类和 2 个扩展类)。
BaseClass.java
基类.java
public class BaseClass{
@Override
public String toString() {
return "BaseClass [list=" + list + ", isA=" + isA + ", x=" + x + "]";
}
public ArrayList<BaseClass> list = new ArrayList<BaseClass>();
protected String isA="BaseClass";
public int x;
}
ExtendedClass1.java
扩展类1.java
public class ExtendedClass1 extends BaseClass{
@Override
public String toString() {
return "ExtendedClass1 [total=" + total + ", number=" + number
+ ", list=" + list + ", isA=" + isA + ", x=" + x + "]";
}
public ExtendedClass1(){
isA = "ExtendedClass1";
}
public Long total;
public Long number;
}
ExtendedClass2.java
扩展类2.java
public class ExtendedClass2 extends BaseClass{
@Override
public String toString() {
return "ExtendedClass2 [total=" + total + ", list=" + list + ", isA="
+ isA + ", x=" + x + "]";
}
public ExtendedClass2(){
isA = "ExtendedClass2";
}
public Long total;
}
CustomDeserializer.java
自定义解串器.java
public class CustomDeserializer implements JsonDeserializer<List<BaseClass>> {
private static Map<String, Class> map = new TreeMap<String, Class>();
static {
map.put("BaseClass", BaseClass.class);
map.put("ExtendedClass1", ExtendedClass1.class);
map.put("ExtendedClass2", ExtendedClass2.class);
}
public List<BaseClass> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
List list = new ArrayList<BaseClass>();
JsonArray ja = json.getAsJsonArray();
for (JsonElement je : ja) {
String type = je.getAsJsonObject().get("isA").getAsString();
Class c = map.get(type);
if (c == null)
throw new RuntimeException("Unknow class: " + type);
list.add(context.deserialize(je, c));
}
return list;
}
}
CustomSerializer.java
自定义序列化程序
public class CustomSerializer implements JsonSerializer<ArrayList<BaseClass>> {
private static Map<String, Class> map = new TreeMap<String, Class>();
static {
map.put("BaseClass", BaseClass.class);
map.put("ExtendedClass1", ExtendedClass1.class);
map.put("ExtendedClass2", ExtendedClass2.class);
}
@Override
public JsonElement serialize(ArrayList<BaseClass> src, Type typeOfSrc,
JsonSerializationContext context) {
if (src == null)
return null;
else {
JsonArray ja = new JsonArray();
for (BaseClass bc : src) {
Class c = map.get(bc.isA);
if (c == null)
throw new RuntimeException("Unknow class: " + bc.isA);
ja.add(context.serialize(bc, c));
}
return ja;
}
}
}
and now this is the code I executed to test the whole thing:
现在这是我为测试整个事情而执行的代码:
public static void main(String[] args) {
BaseClass c1 = new BaseClass();
ExtendedClass1 e1 = new ExtendedClass1();
e1.total = 100L;
e1.number = 5L;
ExtendedClass2 e2 = new ExtendedClass2();
e2.total = 200L;
e2.x = 5;
BaseClass c2 = new BaseClass();
c1.list.add(e1);
c1.list.add(e2);
c1.list.add(c2);
List<BaseClass> al = new ArrayList<BaseClass>();
// this is the instance of BaseClass before serialization
System.out.println(c1);
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(al.getClass(), new CustomDeserializer());
gb.registerTypeAdapter(al.getClass(), new CustomSerializer());
Gson gson = gb.create();
String json = gson.toJson(c1);
// this is the corresponding json
System.out.println(json);
BaseClass newC1 = gson.fromJson(json, BaseClass.class);
System.out.println(newC1);
}
This is my execution:
这是我的执行:
BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]
{"list":[{"total":100,"number":5,"list":[],"isA":"ExtendedClass1","x":0},{"total":200,"list":[],"isA":"ExtendedClass2","x":5},{"list":[],"isA":"BaseClass","x":0}],"isA":"BaseClass","x":0}
BaseClass [list=[ExtendedClass1 [total=100, number=5, list=[], isA=ExtendedClass1, x=0], ExtendedClass2 [total=200, list=[], isA=ExtendedClass2, x=5], BaseClass [list=[], isA=BaseClass, x=0]], isA=BaseClass, x=0]
Some explanations: the trick is done by another Gson inside the serializer/deserializer. I use just isA
field to spot the right class. To go faster, I use a map to associate the isA
string to the corresponding class. Then, I do the proper serialization/deserialization using the second Gson object. I declared it as static so you won't slow serialization/deserialization with multiple allocation of Gson.
一些解释:这个技巧是由序列化器/解串器中的另一个 Gson 完成的。我只使用isA
字段来发现正确的类。为了更快,我使用映射将isA
字符串关联到相应的类。然后,我使用第二个 Gson 对象进行正确的序列化/反序列化。我将其声明为静态,因此您不会因 Gson 的多次分配而减慢序列化/反序列化的速度。
ProYou actually do not write code more code than this, you let Gson do all the work. You have just to remember to put a new subclass into the maps (the exception reminds you of that).
亲你其实不写代码比这多的代码,你让Gson做所有的工作。您只需要记住将一个新的子类放入映射中(异常提醒您这一点)。
ConsYou you have two maps. I think that my implementation can refined a bit to avoid map duplications, but I left them to you (or to future editor, if any).
缺点你有两张地图。我认为我的实现可以稍微改进以避免地图重复,但我把它们留给了你(或未来的编辑器,如果有的话)。
Maybe you want to unificate serialization and deserialization into a unique object, you should be check the TypeAdapter
class or experiment with an object that implements both interfaces.
也许您想将序列化和反序列化统一为一个唯一的对象,您应该检查TypeAdapter
类或尝试实现两个接口的对象。
回答by rpax
There's a simple solution: Gson's RuntimeTypeAdapterFactory(from com.google.code.gson:gson-extras:$gsonVersion
). You don't have to write any serializer, this class does all work for you. Try this with your code:
有一个简单的解决方案:Gson 的 RuntimeTypeAdapterFactory(来自com.google.code.gson:gson-extras:$gsonVersion
)。您不必编写任何序列化程序,这个类可以为您完成所有工作。用你的代码试试这个:
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
RuntimeTypeAdapterFactory<ObixBaseObj> adapter =
RuntimeTypeAdapterFactory
.of(ObixBaseObj.class)
.registerSubtype(ObixBaseObj.class)
.registerSubtype(ObixOp.class);
Gson gson2=new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(adapter).create();
Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));
System.out.println("---------------------");
System.out.println(gson2.toJson(lobbyObj));
}
Output:
输出:
{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch","children":[]}]}
---------------------
{
"type": "ObixBaseObj",
"obix": "obj",
"is": "obix:Lobby",
"children": [
{
"type": "ObixOp",
"in": "obix:BatchIn",
"out": "obix:BatchOut",
"obix": "op",
"name": "batch",
"children": []
}
]
}
EDIT:Better working example.
编辑:更好的工作示例。
You said that there are about 25 classes that inherits from ObixBaseObj
.
你说大约有 25 个类继承自ObixBaseObj
.
We start writing a new class, GsonUtils
我们开始编写一个新类,GsonUtils
public class GsonUtils {
private static final GsonBuilder gsonBuilder = new GsonBuilder()
.setPrettyPrinting();
public static void registerType(
RuntimeTypeAdapterFactory<?> adapter) {
gsonBuilder.registerTypeAdapterFactory(adapter);
}
public static Gson getGson() {
return gsonBuilder.create();
}
Every time we need a Gson
object, instead of calling new Gson()
, we will call
每次我们需要一个Gson
对象时new Gson()
,我们将调用而不是调用
GsonUtils.getGson()
We add this code to ObixBaseObj:
我们将此代码添加到 ObixBaseObj 中:
public class ObixBaseObj {
protected String obix;
private String display;
private String displayName;
private String name;
private String is;
private ArrayList<ObixBaseObj> children = new ArrayList<ObixBaseObj>();
// new code
private static final RuntimeTypeAdapterFactory<ObixBaseObj> adapter =
RuntimeTypeAdapterFactory.of(ObixBaseObj.class);
private static final HashSet<Class<?>> registeredClasses= new HashSet<Class<?>>();
static {
GsonUtils.registerType(adapter);
}
private synchronized void registerClass() {
if (!registeredClasses.contains(this.getClass())) {
registeredClasses.add(this.getClass());
adapter.registerSubtype(this.getClass());
}
}
public ObixBaseObj() {
registerClass();
obix = "obj";
}
Why? because every time this class or a children class of ObixBaseObj
is instantiated,
the class it's gonna be registered in the RuntimeTypeAdapter
为什么?因为每次ObixBaseObj
实例化这个类或子类时,它都会被注册到RuntimeTypeAdapter
In the child classes, only a minimal change is needed:
在子类中,只需要进行最小的更改:
public class ObixOp extends ObixBaseObj {
private String in;
private String out;
public ObixOp() {
super();
obix = "op";
}
public ObixOp(String in, String out) {
super();
obix = "op";
this.in = in;
this.out = out;
}
Working example:
工作示例:
public static void main(String[] args) {
ObixBaseObj lobbyObj = new ObixBaseObj();
lobbyObj.setIs("obix:Lobby");
ObixOp batchOp = new ObixOp();
batchOp.setName("batch");
batchOp.setIn("obix:BatchIn");
batchOp.setOut("obix:BatchOut");
lobbyObj.addChild(batchOp);
Gson gson = GsonUtils.getGson();
System.out.println(gson.toJson(lobbyObj));
}
Output:
输出:
{
"type": "ObixBaseObj",
"obix": "obj",
"is": "obix:Lobby",
"children": [
{
"type": "ObixOp",
"in": "obix:BatchIn",
"out": "obix:BatchOut",
"obix": "op",
"name": "batch",
"children": []
}
]
}
I hope it helps.
我希望它有帮助。
回答by Jeff
I appreciate the other answers here that led me on my path to solving this issue. I used a combination of RuntimeTypeAdapterFactory
with Reflection.
我很欣赏这里的其他答案,这些答案让我走上了解决这个问题的道路。我使用了RuntimeTypeAdapterFactory
与Reflection的组合。
I also created a helper class to make sure a properly configured Gson was used.
我还创建了一个辅助类,以确保使用正确配置的 Gson。
Within a static block inside the GsonHelper class, I have the following code go through my project to find and register all of the appropriate types. All of my objects that will go through JSON-ification are a subtype of Jsonable. You will want to change the following:
在 GsonHelper 类内的静态块中,我有以下代码遍历我的项目以查找和注册所有适当的类型。我所有将通过 JSON 化的对象都是 Jsonable 的子类型。您将需要更改以下内容:
- my.project in
Reflections
should be your package name. Jsonable.class
is my base class. Substitute yours.I like having the field show the full canonical name, but clearly if you don't want / need it, you can leave out that part of the call to register the subtype. The same thing goes for
className
in theRuntimeAdapterFactory
; I have data items already using thetype
field.private static final GsonBuilder gsonBuilder = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .excludeFieldsWithoutExposeAnnotation() .setPrettyPrinting(); static { Reflections reflections = new Reflections("my.project"); Set<Class<? extends Jsonable>> allTypes = reflections.getSubTypesOf(Jsonable.class); for (Class< ? extends Jsonable> serClass : allTypes){ Set<?> subTypes = reflections.getSubTypesOf(serClass); if (subTypes.size() > 0){ RuntimeTypeAdapterFactory<?> adapterFactory = RuntimeTypeAdapterFactory.of(serClass, "className"); for (Object o : subTypes ){ Class c = (Class)o; adapterFactory.registerSubtype(c, c.getCanonicalName()); } gsonBuilder.registerTypeAdapterFactory(adapterFactory); } } } public static Gson getGson() { return gsonBuilder.create(); }
- my.project in
Reflections
应该是你的包名。 Jsonable.class
是我的基类。代替你的。我喜欢让该字段显示完整的规范名称,但很明显,如果您不想要/不需要它,您可以省略注册子类型的调用的那部分。同样的事情也适用
className
于RuntimeAdapterFactory
; 我有数据项已经在使用该type
字段。private static final GsonBuilder gsonBuilder = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .excludeFieldsWithoutExposeAnnotation() .setPrettyPrinting(); static { Reflections reflections = new Reflections("my.project"); Set<Class<? extends Jsonable>> allTypes = reflections.getSubTypesOf(Jsonable.class); for (Class< ? extends Jsonable> serClass : allTypes){ Set<?> subTypes = reflections.getSubTypesOf(serClass); if (subTypes.size() > 0){ RuntimeTypeAdapterFactory<?> adapterFactory = RuntimeTypeAdapterFactory.of(serClass, "className"); for (Object o : subTypes ){ Class c = (Class)o; adapterFactory.registerSubtype(c, c.getCanonicalName()); } gsonBuilder.registerTypeAdapterFactory(adapterFactory); } } } public static Gson getGson() { return gsonBuilder.create(); }
回答by ruediste
I created a type adapter factory that uses an annotation and ClassGraphto discover subclasses and supports multiple serialization styles (Type Property, Property, Array). See githubfor source code and maven coordinates.
我创建了一个类型适配器工厂,它使用注释和ClassGraph来发现子类并支持多种序列化样式(类型属性、属性、数组)。有关源代码和 maven 坐标,请参阅github。