Java Gson序列化多态对象列表

Java Gson序列化多态对象列表,java,json,serialization,gson,Java,Json,Serialization,Gson,我正在尝试使用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

我正在尝试使用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;
    }
        ...
}

我意识到我可以为此使用适配器,但问题是我正在序列化基类类型的集合
ObixBaseObj
。大约有25个类继承自此。我怎样才能使它优雅地工作呢?

我认为定制序列化器/反序列化器是唯一可以继续工作的方法,我试图向您推荐我发现的实现它的最简洁的方法。我很抱歉没有使用您的类,但是想法是一样的(我只想要至少一个基类和两个扩展类)

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;
   
 }
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;
    
}
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;
    
}
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;

    }

}
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;
        }
    }
}
ExtendedClass2.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;
   
 }
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;
    
}
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;
    
}
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;

    }

}
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;
        }
    }
}
CustomDeserializer.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;
   
 }
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;
    
}
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;
    
}
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;

    }

}
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;
        }
    }
}
一些解释:这个技巧是由序列化器/反序列化器中的另一个Gson完成的。我使用just
isA
字段来确定正确的类。为了更快,我使用映射将
isA
字符串与相应的类相关联。然后,我使用第二个Gson对象执行适当的序列化/反序列化。我将其声明为静态,这样您就不会因为多次分配Gson而减慢序列化/反序列化的速度

Pro 实际上,您编写的代码并不比这多,而是让Gson完成所有工作。您只需记住在映射中放入一个新的子类(异常提醒您这一点)

缺点 你有两张地图。我认为我的实现可以进行一些改进,以避免地图重复,但我把它们留给了您(或者未来的编辑器,如果有的话)


也许您想将序列化和反序列化统一到一个唯一的对象中,您应该检查
TypeAdapter
类或使用实现这两个接口的对象进行实验。

有一个简单的解决方案:(来自
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));

}
编辑:更好的工作示例

您说过大约有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();
    }
我们将此代码添加到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";
    }
工作示例:

{"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": []
    }
  ]
}
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": []
    }
  ]
}
输出:

{"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": []
    }
  ]
}
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": []
    }
  ]
}

我希望这能有所帮助。

我很感激这里的其他答案,这些答案引导我走上了解决这个问题的道路。我使用了
RuntimeTypeAdapterFactory
与的组合

我还创建了一个helper类,以确保使用了正确配置的Gson

在GsonHelper类内的静态块中,我有以下代码遍历我的项目以查找和注册所有适当的类型。我所有将通过JSON转换的对象都是Jsonable的子类型。 您将要更改以下内容:

  • Reflections
    中的my.project应该是您的包名
  • Jsonable.class
    是我的基类。代替你的
  • 我喜欢让字段显示完整的规范名称,但是如果您不想/不需要它,可以省略调用的这一部分来注册子类型。在
    RuntimeAdapterFactory
    中的
    className
    也是如此;我的数据项已经在使用
    类型
    字段

    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();
    }
    
    private static final GsonBuilder GsonBuilder=new GsonBuilder()
    .setDateFormat(“yyyy-MM-dd'T'HH:MM:ssZ”)
    .excludeFieldsWithoutExposeAnnotation()
    .setPrettyPrinting();
    静止的{
    反思=新的反思(“我的项目”);
    Set subtype=reflections.getsubscriptsof(serClass);
    如果(子类型.size()>0){
    RuntimeTypeAdapterFactoryAdapterFactory=RuntimeTypeAdapterFactory.of(serClass,“className”);
    用于(对象o:子类型){
    c类=(o类);
    registerSubtype(c,c.getCanonicalName());
    }
    gsonBuilder.registerTypeAdapterFactory(adapterFactory);
    }
    }
    }
    公共静态Gson getGson(){
    返回gsonBuilder.create();
    }
    

  • 我创建了一个类型适配器工厂,它使用注释和发现子类,并支持多种序列化样式(类型属性、属性、数组)。有关源代码和maven坐标,请参阅。

    我还没有检查反序列化,但对于序列化,存在一个问题。如果您检查我的ObixBaseObj类,我有一个基类型的arraylist,它被添加为其子类的一部分。在您的示例中,您直接序列化了基类的arraylist,但在我的示例中,我序列化了一个包含多态对象数组列表的基类对象。在这种情况下,序列化无法正常工作。更具体地说,如果我将私有ArrayList baseList成员添加到基类中,将从基类继承的对象添加到该列表(ExtendedClass1,2),然后尝试序列化基类的单个实例,则无法正确序列化包含的ArrayList。实际上,反序列化也有类似的问题。所有内容都被反序列化为基类。我将我的示例更改为类似于您的示例。我想现在您的问题已经解决了,请参见上面我编辑的答案。al.getClass()返回一个通用的java.util.ArrayList类型,但它并不总是正确的。使用类型listType=newTypeToken(){}.getType();获取java.util.ArrayList。取而代之的是从wiki页面获取或创建wiki页面。RuntimeTypeAdapterFactory现在可用吗?RuntimeTypeAdapterFactory对根对象的工作不正确,我注册了一个问题:@mavixce有任何线索说明为什么这个类没有随gson工件一起提供吗?Thanks@Shyri,RuntimeTypeAdapterFactory位于gson库的extras包中。我不是谷歌员工,也不是gson图书馆的贡献者