简体   繁体   中英

Deserialize recursive polymorphic class in GSON

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

class Simple implements Recursive { ... }

How do I deserialize this json:

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

using Google GSON?

To deserialize your JSON you need a custom deserializer for your Recursive interface. In that kind of class you need to examine your JSON and decide what kind of class to instantiate as the type field in the JSON itself. Here you have a basic deserializer I wrote for you example.

Of course it can be improve to manage borderline cases (for example, what happens if you do not have type field?).

package stackoverflow.questions;

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

import stackoverflow.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);

   }

}

This is the result of my code:

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

Sorry for replying too late (as this question is already answered). But I think I can give my 2 cents to this issue.

As @Parobay has said before you can add an special {"type": "complex"} parameter in order to know which class do you want to deserialize. But as said before, you have to move all your data to an {"instance": {}} subobject which feels akward as you have to keep control manually of the instances created in a big switch .

You can use instead a TypeFactory class to handle this situation better. More precisely, there are one special factory (which is not bundled currently with Gson and should) called RuntimeTypeAdapterFactory . Copying what documentation says about this class:

Adapts values whose runtime type may differ from their declaration type. This is necessary when a field's type is not the same type that GSON should create when deserializing that field. For example, consider these types:

 { \n    abstract class Shape { \n      int x; \n      int y; \n    } \n    class Circle extends Shape { \n      int radius; \n    } \n    class Rectangle extends Shape { \n      int width; \n      int height; \n    } \n    class Diamond extends Shape { \n      int width; \n      int height; \n    } \n    class Drawing { \n      Shape bottomShape; \n      Shape topShape; \n    } }  

Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in this drawing a rectangle or a diamond?

 { \n    { \n      "bottomShape": { \n        "width": 10, \n        "height": 5, \n        "x": 0, \n        "y": 0 \n      }, \n      "topShape": { \n        "radius": 2, \n        "x": 4, \n        "y": 1 \n      } \n    }}  
This class addresses this problem by adding type information to the serialized JSON and honoring that type information when the JSON is deserialized:
 { \n    { \n      "bottomShape": { \n        "type": "Diamond", \n        "width": 10, \n        "height": 5, \n        "x": 0, \n        "y": 0 \n      }, \n      "topShape": { \n        "type": "Circle", \n        "radius": 2, \n        "x": 4, \n        "y": 1 \n      } \n    }}  
Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are configurable.

So you only have to create your Gson object with:

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

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

And voilá, you have automatic (de)serialization of objects. I created a variation of this Factory that instead of expecting a type parameter, tries to discover what kind of object is by running a custom regular expression (that way you can avoid to create that extra parameter, or when you don't have full control of the API).

Here I give you the two links with the sources:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM