How to handle deserializing with polymorphism?

前端 未结 4 513
既然无缘
既然无缘 2020-11-27 17:04

I have a class like:

public class Barn {
    String type;
    Animal animal;
}

public class Horse extends Animal {
}

public class Cow extends Animal {
}


        
相关标签:
4条回答
  • 2020-11-27 17:26

    You don't need to write your own Adapter.

    Finally gson has its own RuntimeTypeAdapterFactory to handle java polymorphism but unfortunately it's not part of the gson core library (in order to use it you must copy and paste the class into your project).

    Suppose that you have this Animal.class:

    public class Animal {
        protected String name;
        protected String type;
    
        public Animal(String name, String type) {
            this.name = name;
            this.type = type;
        }
    }
    

    And Horse.class:

    public class Horse extends Animal {
        public Horse(String name) {
            super(name, "horse");
        }
    }
    

    You can initialize the RuntimeTypeAdapterFactory using the code below:

    RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
    .of(Animal.class, "type")
    .registerSubtype(Horse.class, "horse")
    .registerSubtype(Cow.class, "cow");
    
    Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
        .create();
    

    Here you can find a full working example.

    0 讨论(0)
  • 2020-11-27 17:28

    In the Gson project code base is the RuntimeTypeAdapter, which reportedly works well for polymorphic serialization and deserialization. I don't think I've yet tried to use it. See http://code.google.com/p/google-gson/issues/detail?id=231 for more info. Note, it hasn't yet been included in any Gson releases.

    If use of it doesn't fit your needs, then custom deserialization processing is necessary. Following is one such approach, assuming you want to use the JSON structure demonstrated. (I'd take a somewhat different approach, if the JSON structure could be different.)

    import java.lang.reflect.Type;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.JsonDeserializationContext;
    import com.google.gson.JsonDeserializer;
    import com.google.gson.JsonElement;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParseException;
    import com.google.gson.reflect.TypeToken;
    
    public class App
    {
      public static void main(String[] args)
      {
        Barn[] barns = {new Barn(), new Barn()};
        barns[0].type = "horse";
        barns[0].animal = new Horse();
        barns[1].type = "cow";
        barns[1].animal = new Cow();
    
        String json = new Gson().toJson(barns);
        // [{"type":"horse","animal":{}},{"type":"cow","animal":{}}]
    
        BarnDeserializer deserializer = new BarnDeserializer("type");
        deserializer.registerBarnType("horse", Horse.class);
        deserializer.registerBarnType("cow", Cow.class);
        Gson gson = new GsonBuilder().registerTypeAdapter(Barn.class, deserializer).create();
    
        List<Barn> barns2= gson.fromJson(json, new TypeToken<List<Barn>>(){}.getType());
        for (Barn barn : barns2)
        {
          System.out.println(barn.animal.getClass());
        }
      }
    }
    
    class BarnDeserializer implements JsonDeserializer<Barn>
    {
      String barnTypeElementName;
      Gson gson;
      Map<String, Class<? extends Animal>> barnTypeRegistry;
    
      BarnDeserializer(String barnTypeElementName)
      {
        this.barnTypeElementName = barnTypeElementName;
        gson = new Gson();
        barnTypeRegistry = new HashMap<>(); // Java 7 required for this syntax.
      }
    
      void registerBarnType(String barnTypeName, Class<? extends Animal> animalType)
      {
        barnTypeRegistry.put(barnTypeName, animalType);
      }
    
      @Override
      public Barn deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 
          throws JsonParseException
      {
        JsonObject barnObject = json.getAsJsonObject();
        JsonElement animalTypeElement = barnObject.get(barnTypeElementName);
        Barn barn = new Barn();
        barn.type = animalTypeElement.getAsString(); 
        Class<? extends Animal> animalType = barnTypeRegistry.get(barn.type);
        barn.animal = gson.fromJson(barnObject.get("animal"), animalType);
        return barn;
      }
    }
    
    class Barn {String type; Animal animal;}
    class Animal {}
    class Horse extends Animal {}
    class Cow extends Animal {}
    
    0 讨论(0)
  • 2020-11-27 17:28

    You could use Gson Fire for this. The code would look something like this:

    GsonFireBuilder builder = new GsonFireBuilder()
        .registerTypeSelector(Barn.class, new TypeSelector<Barn>() {
            @Override
            public Class<? extends Barn> getClassForElement(JsonElement readElement) {
                String type = readElement.getAsJsonObject().get("type").getAsString();
                if(type.equals("horse")){
                    return Horse.class; 
                } else if(type.equals("cow")) {
                    return Cow.class;
                } else {
                    return null; //returning null will trigger Gson's default behavior
                }
            }
        });
    Gson gson = builder.createGson();
    
    0 讨论(0)
  • 2020-11-27 17:49

    I'm also interested in the feature and I've added a feature request to GSon

    https://github.com/google/gson/issues/869

    0 讨论(0)
提交回复
热议问题