Gson序列化多态对象列表

l46*_*kok 42 java serialization json 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.addChild(batchOp);

Gson gson = new Gson();
System.out.println(gson.toJson(lobbyObj));
Run Code Online (Sandbox Code Playgroud)

这是结果:

 {"obix":"obj","is":"obix:Lobby","children":[{"obix":"op","name":"batch"}]}
Run Code Online (Sandbox Code Playgroud)

序列化大多的作品,除了它缺少继承成员的内容(尤其是obix:BatchInobixBatchout字符串丢失).这是我的基类:

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;
    }
        ...
}
Run Code Online (Sandbox Code Playgroud)

这是我继承的类(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以使用适配器,但问题是我正在序列化基类类型的集合ObixBaseObj.大约有25个类继承自此.我怎样才能优雅地完成这项工作?

rpa*_*pax 50

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

}
Run Code Online (Sandbox Code Playgroud)

输出:

{"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": []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

编辑: 更好的工作示例.

你说有大约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();
    }
Run Code Online (Sandbox Code Playgroud)

每次我们需要一个Gson对象,而不是打电话new Gson(),我们会打电话

GsonUtils.getGson()
Run Code Online (Sandbox Code Playgroud)

我们将此代码添加到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";
    }
Run Code Online (Sandbox Code Playgroud)

为什么?因为每次这个类或子类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;
    }
Run Code Online (Sandbox Code Playgroud)

工作范例:

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

    }
Run Code Online (Sandbox Code Playgroud)

输出:

{
  "type": "ObixBaseObj",
  "obix": "obj",
  "is": "obix:Lobby",
  "children": [
    {
      "type": "ObixOp",
      "in": "obix:BatchIn",
      "out": "obix:BatchOut",
      "obix": "op",
      "name": "batch",
      "children": []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

我希望它有所帮助.

  • 您需要手动将其添加到您的代码中. (3认同)
  • RuntimeTypeAdapterFactory现在可用吗? (2认同)
  • @mavixce任何线索为什么这个类没有附带gson工件?谢谢 (2认同)

gia*_*olo 41

我认为自定义序列化器/解串器是唯一的方法,我试图向您提出实现它的最紧凑的方法.我为不使用你的类而道歉,但这个想法是一样的(我只想要至少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;

 }
Run Code Online (Sandbox Code Playgroud)

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;

}
Run Code Online (Sandbox Code Playgroud)

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;

}
Run Code Online (Sandbox Code Playgroud)

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;

    }

}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在这是我为测试整个事情而执行的代码:

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

}
Run Code Online (Sandbox Code Playgroud)

这是我的执行:

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]
Run Code Online (Sandbox Code Playgroud)

一些解释:这个技巧是由串行器/解串器中的另一个Gson完成的.我只使用isA字段来发现正确的类.为了更快,我使用地图将isA字符串与相应的类相关联.然后,我使用第二个Gson对象进行正确的序列化/反序列化.我将其声明为静态,因此您不会通过多次分配Gson来减慢序列化/反序列化.

Pro 你实际上不会编写代码比这更多的代码,你让Gson做所有的工作.您只需记住将新子类放入映射中(异常会提醒您).

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

也许你想要将序列化和反序列化解析为一个唯一的对象,你应该检查TypeAdapter类或试验一个实现两个接口的对象.


小智 5

我很欣赏这里的其他答案,这些答案使我走上了解决这个问题的道路。我用的组合RuntimeTypeAdapterFactory反思

我还创建了一个帮助程序类,以确保使用了正确配置的Gson。

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

  1. 其中的my.project Reflections应该是您的程序包名称。
  2. Jsonable.class是我的基础课。用你的代替。
  3. 我喜欢让该字段显示完整的规范名称,但是很显然,如果您不需要/不需要它,可以省去调用的那一部分来注册子类型。同样的事情classNameRuntimeAdapterFactory; 我已经有使用该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();
    }
    
    Run Code Online (Sandbox Code Playgroud)