Gson сериализует список полиморфных объектов

Я пытаюсь сериализовать / десериализовать объект, который включает polymorphism, в JSON, используя Gson.

Это мой код для сериализации:

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 ). Вот мой базовый class:

 public class ObixBaseObj { protected String obix; private String display; private String displayName; private ArrayList children; public ObixBaseObj() { obix = "obj"; } public void setName(String name) { this.name = name; } ... } 

Вот что мой унаследованный class (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; } } 

Я понимаю, что я могу использовать адаптер для этого, но проблема в том, что я сериализую коллекцию базового classа типа ObixBaseObj . Наследуется от этого 25 classов. Как я могу сделать эту работу элегантно?

Я думаю, что пользовательский сериализатор / десериализатор – единственный способ продолжения, и я попытался предложить вам самый компактный способ реализовать его, который я нашел. Я извиняюсь за то, что не использовал ваши classы, но идея такая же (мне просто нужен как минимум 1 базовый class и 2 расширенных classа).

BaseClass.java

 public class BaseClass{ @Override public String toString() { return "BaseClass [list=" + list + ", isA=" + isA + ", x=" + x + "]"; } public ArrayList list = new ArrayList(); 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> { private static Map map = new TreeMap(); static { map.put("BaseClass", BaseClass.class); map.put("ExtendedClass1", ExtendedClass1.class); map.put("ExtendedClass2", ExtendedClass2.class); } public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { List list = new ArrayList(); 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> { private static Map map = new TreeMap(); static { map.put("BaseClass", BaseClass.class); map.put("ExtendedClass1", ExtendedClass1.class); map.put("ExtendedClass2", ExtendedClass2.class); } @Override public JsonElement serialize(ArrayList 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 al = new ArrayList(); // 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 чтобы определить правильный class. Чтобы идти быстрее, я использую карту для сопоставления строки isA с соответствующим classом. Затем я делаю правильную сериализацию / десериализацию с использованием второго объекта Gson. Я объявил это как статический, поэтому вы не будете замедлять сериализацию / десериализацию с множественным распределением Gson.

Pro Вы на самом деле не пишете код больше кода, чем этот, вы позволите Gson выполнить всю работу. Вы должны помнить о том, чтобы добавить новый подclass в карты (это напоминает об этом исключение).

Минусы. У вас есть две карты. Я думаю, что моя реализация может немного уточнить, чтобы избежать дублирования карт, но я оставил их вам (или будущему редактору, если таковые имеются).

Возможно, вы хотите унифицировать сериализацию и десериализацию в уникальный объект, вы должны проверить class TypeAdapter или экспериментировать с объектом, который реализует оба интерфейса.

Существует простое решение: RuntimeTypeAdapterFactory от Gson . Вам не нужно писать какой-либо сериализатор, этот class все работает для вас. Попробуйте это с помощью своего кода:

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

EDIT: лучший рабочий пример.

Вы сказали, что существует около 25 classов, которые наследуются от ObixBaseObj .

Мы начинаем писать новый class, 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 children = new ArrayList(); // new code private static final RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of(ObixBaseObj.class); private static final HashSet> registeredClasses= new HashSet>(); 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"; } 

Зачем? потому что каждый раз, когда этот class или ObixBaseObj class ObixBaseObj , class, который он будет зарегистрирован в RuntimeTypeAdapter

В дочерних classах требуется только минимальное изменение:

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

Я надеюсь, что это помогает.

Я ценю другие ответы здесь, которые привели меня на мой путь к решению этой проблемы. Я использовал комбинацию RuntimeTypeAdapterFactory с Reflection .

Я также создал вспомогательный class, чтобы убедиться, что был правильно настроен Gson.

Внутри статического блока внутри classа GsonHelper у меня есть следующий код, который проходит через мой проект, чтобы найти и зарегистрировать все соответствующие типы. Все мои объекты, которые пройдут через JSON-ification, являются подтипом Jsonable. Вы захотите изменить следующее:

  1. my.project в Reflections должен быть вашим именем пакета.
  2. Jsonable.class – мой базовый class. Замените свое.
  3. Мне нравится, когда поле показывает полное каноническое имя, но ясно, если вы его не хотите / нуждаетесь, вы можете оставить эту часть вызова для регистрации подтипа. То же самое относится к 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> 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(); } 
  • Что библиотека JSON хорошо работает для вас в .NET?
  • Как писать и читать java-сериализованные объекты в файл
  • Конвертировать словарь в JSON в Swift
  • ef4 причина Циркулярная ссылка в веб-службе
  • Неверный регистр
  • Каков самый простой способ генерации xml в c ++?
  • Имеются ли атрибуты DataContract для WCF
  • Как сериализовать объект Exception в C #?
  • Что такое сериализация объектов?
  • Сделать пользовательский class Serializable в Objective-c / iPhone?
  • Как называются конструкторы во время сериализации и десериализации?
  • Давайте будем гением компьютера.