Десериализовать рекурсивный полиморфный класс в GSON

class Complex implements Recursive {
  Map<String, Recursive> map;
  ...
}

class Simple implements Recursive { ... }

Как я могу десериализовать этот JSON:

{
  "type" : "complex",
  "map" : {
     "a" : {
        "type" : "simple"
     },
     "b" : {
        "type" : "complex",
        "map" : {
            "ba" : {
                "type" : "simple"
        } 
     } 
  }
}

с помощью Google GSON?

2 ответа

Решение

Для десериализации вашего JSON вам нужен специальный десериализатор для вашего рекурсивного интерфейса. В классе такого типа вам нужно проверить свой JSON и решить, какой класс создать в качестве поля типа в самом JSON. Вот вам основной десериализатор, который я написал для вас.

Конечно, можно улучшить управление пограничными случаями (например, что произойдет, если у вас нет поля типа?).

package stackru.questions;

import java.lang.reflect.Type;
import java.util.*;

import stackru.questions.Q20254329.*;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

public class Q20327670 {

   static class Complex implements Recursive {
      Map<String, Recursive> map;

      @Override
      public String toString() {
         return "Complex [map=" + map + "]";
      }

   }

   static class Simple implements Recursive {

      @Override
      public String toString() {
         return "Simple []";
      }
   }

   public static class RecursiveDeserializer implements JsonDeserializer<Recursive> {

      public Recursive deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
         Recursive r = null;
         if (json == null)
            r = null;
         else {
            JsonElement t = json.getAsJsonObject().get("type");
            String type = null;
            if (t != null) {
               type = t.getAsString();

               switch (type) {
               case "complex": {
                  Complex c = new Complex();
                  JsonElement e = json.getAsJsonObject().get("map");
                  if (e != null) {
                     Type mapType = new TypeToken<Map<String, Recursive>>() {}.getType();
                     c.map = context.deserialize(e, mapType);
                  }
                  r = c;
                  break;
               }
               case "simple": {
                  r = new Simple();
                  break;
               }
               // remember to manage default..
               }

            }
         }
         return r;
      }

   }

   public static void main(String[] args) {
      String json = " {                                         " + 
                    "    \"type\" : \"complex\",                " + 
                    "    \"map\" : {                            " + 
                    "       \"a\" : {                           " +
                    "          \"type\" : \"simple\"            " +
                    "       },                                  " + 
                    "       \"b\" : {                           " +
                    "          \"type\" : \"complex\",          " + 
                    "          \"map\" : {                      " + 
                    "              \"ba\" : {                   " +
                    "                  \"type\" : \"simple\"    " +
                    "          }                                " +
                    "       }                                   " +
                    "    }                                      " +
                    "  }  }                                     ";

      GsonBuilder gb = new GsonBuilder();
      gb.registerTypeAdapter(Recursive.class, new RecursiveDeserializer());

      Gson gson = gb.create();
      Recursive r = gson.fromJson(json, Recursive.class);

      System.out.println(r);

   }

}

Это результат моего кода:

Complex [map={a=Simple [], b=Complex [map={ba=Simple []}]}]

Извините за ответ слишком поздно (так как на этот вопрос уже дан ответ). Но я думаю, что могу дать свои 2 цента на этот вопрос.

Как сказал @Parobay, прежде чем вы сможете добавить специальный {"type": "complex"} Параметр, чтобы узнать, какой класс вы хотите десериализовать. Но, как уже было сказано, вы должны переместить все свои данные в {"instance": {}} подобъект, который чувствует себя неловко, поскольку вы должны вручную контролировать экземпляры, созданные в большом switch,

Вы можете использовать вместо TypeFactory класс, чтобы справиться с этой ситуацией лучше. Точнее, есть одна специальная фабрика (которая в настоящее время не связана с Gson и должна) RuntimeTypeAdapterFactory, Копирование того, что документация говорит об этом классе:

Адаптирует значения, тип времени выполнения которых может отличаться от их типа объявления. Это необходимо, когда тип поля отличается от типа, который GSON должен создать при десериализации этого поля. Например, рассмотрим эти типы:

 {
    абстрактный класс Shape {
      int x;
      int y;
    }
    Класс Circle расширяет Shape {
      внутренний радиус;
    }
    Класс Rectangle расширяет форму {
      int width;
      высота int;
    }
    класс Diamond расширяет форму {
      int width;
      высота int;
    }
    класс Drawing {
      Shape bottomShape;
      Shape topShape;
    }   } 

Без дополнительной информации о типах сериализованный JSON является неоднозначным. Является ли нижняя фигура на этом рисунке прямоугольником или ромбом?

 {
    {
      "bottomShape": {
        "ширина": 10,
        "высота": 5,
        "х": 0,
        "у": 0
      },
      "topShape": {
        "радиус": 2,
        "х": 4,
        "у": 1
      }
    }} 
Этот класс решает эту проблему, добавляя информацию о типе в сериализованный JSON и обрабатывая эту информацию о типе при десериализации JSON:
 {
    {
      "bottomShape": {
        "type": "Diamond",
        "ширина": 10,
        "высота": 5,
        "х": 0,
        "у": 0
      },
      "topShape": {
        "тип": "круг",
        "радиус": 2,
        "х": 4,
        "у": 1
      }
    }} 
И имя поля типа ({@code "type"}), и метки типа ({@code "Rectangle"}) можно настраивать.

Так что вам нужно только создать свой объект Gson с:

RuntimeTypeAdapter<Shape> shapeAdapter
    = RuntimeTypeAdapter.of(Shape.class, "type")
      .registerSubtype(Rectangle.class, "Rectangle");    

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Shape.class, shapeAdapter)
    .create();

И вуаля, у вас есть автоматическая (де) сериализация объектов. Я создал вариант этой фабрики, который вместо того, чтобы ожидать type Параметр, пытается выяснить, что это за объект, запустив пользовательское регулярное выражение (таким образом вы можете избежать создания этого дополнительного параметра или когда у вас нет полного контроля над API).

Здесь я даю вам две ссылки с источниками:

Другие вопросы по тегам