我正在尝试使用Gson将涉及多态的对象序列化/反序列化为JSON。
这是我的序列化代码:
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));
结果如下:
{"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch"}]}
序列化主要起作用,但缺少继承成员的内容(特别是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;
}
...
}
这是我继承的类(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;
}
}
我意识到我可以使用适配器,但问题是我正在序列化基类类型ObixBaseObj
的集合。大约有25个类继承自此。我怎样才能优雅地完成这项工作?
答案 0 :(得分:49)
有一个简单的解决方案:Gson's 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));
}
<强>输出:强>
{"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": []
}
]
}
编辑:更好的工作示例。
你说大约有25个类继承自ObixBaseObj
。
我们开始编写一个新类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();
}
每次我们需要Gson
个对象时,我们都会调用
new Gson()
GsonUtils.getGson()
我们将此代码添加到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";
}
为什么呢?因为每次实例化此类或ObixBaseObj
的子类时,
该课程将在RuntimeTypeAdapter
在子课程中,只需要进行最小的更改:
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;
}
工作示例:
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));
}
<强>输出:强>
{
"type": "ObixBaseObj",
"obix": "obj",
"is": "obix:Lobby",
"children": [
{
"type": "ObixOp",
"in": "obix:BatchIn",
"out": "obix:BatchOut",
"obix": "op",
"name": "batch",
"children": []
}
]
}
我希望它有所帮助。
答案 1 :(得分:39)
我认为自定义序列化器/解串器是唯一可行的方法,我试图向您提出实现它的最紧凑的方法。我为没有使用你的类而道歉,但想法是一样的(我只想要至少1个基类和2个扩展类)。
<强> BaseClass.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 强>
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 强>
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 强>
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;
}
}
}
现在这是我为测试整个事情而执行的代码:
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);
}
这是我的执行:
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]
一些解释:这个技巧是由串行器/解串器中的另一个Gson完成的。我只使用isA
字段来查找正确的类。为了更快,我使用地图将isA
字符串与相应的类相关联。然后,我使用第二个Gson对象进行正确的序列化/反序列化。我将其声明为静态,因此您不会通过多次分配Gson来减慢序列化/反序列化。
<强>临强> 你实际上不会编写代码比这更多的代码,你让Gson做所有的工作。你只需要记住将一个新的子类放入映射中(异常会提醒你)。
<强>缺点强> 你有两张地图。我认为我的实现可以稍微改进以避免地图重复,但我把它们留给你(或者将它们留给未来的编辑器,如果有的话)。
也许您想要将序列化和反序列化解析为唯一对象,您应该检查TypeAdapter
类或试验实现这两个接口的对象。
答案 2 :(得分:4)
我很欣赏这里的其他答案,这使我走上了解决这个问题的道路。我使用了RuntimeTypeAdapterFactory
和Reflection的组合。
我还创建了一个帮助器类,以确保使用了正确配置的Gson。
在GsonHelper类的一个静态块中,我有以下代码通过我的项目来查找和注册所有适当的类型。我将通过JSON-enification的所有对象都是Jsonable的子类型。 您需要更改以下内容:
Reflections
中的my.project应该是您的包名。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();
}
答案 3 :(得分:1)
我创建了一个类型适配器工厂,该工厂使用批注和ClassGraph发现子类并支持多种序列化样式(类型属性,属性,数组)。有关源代码和Maven坐标,请参见github。