Подтвердить что ты не робот

Как обращаться с десериализацией с полиморфизмом?

У меня есть класс вроде:

public class Barn {
    String type;
    Animal animal;
}

public class Horse extends Animal {
}

public class Cow extends Animal {
}

и я хочу сериализовать их список:

List<Barn> barns = new ArrayList<Barn>();

Barn barn1 = new Barn();
barn1.setType("horse");
barn1.setAnimal(new Horse());
barns.add(barn1);

Barn barn2 = new Barn();
barn2.setType("cow");
barn2.setAnimal(new Cow());
barns.add(barn2);

...

Group<Barn> barns = gson.fromJson(serialized);   

Когда я сериализую, информация типа будет потеряна для атрибута Animal. Есть ли способ, по которому я мог бы установить прослушиватель парсера, чтобы я мог предоставить правильный класс для десериализации, как когда каждый элемент в списке встречается? Это была идея ручной подачи строки, описывающей тип класса.

Спасибо

4b9b3361

Ответ 1

В базе кода проекта Gson используется RuntimeTypeAdapter, который, как сообщается, хорошо подходит для полиморфной сериализации и десериализации. Я не думаю, что я еще не пытался его использовать. Дополнительную информацию см. на странице http://code.google.com/p/google-gson/issues/detail?id=231. Заметьте, что он еще не был включен ни в какие выпуски Gson.

Если использование этого не соответствует вашим потребностям, необходима специальная обработка десериализации. Ниже приведен один такой подход, предполагая, что вы хотите продемонстрировать структуру JSON. (Я бы выбрал несколько иной подход, если бы структура JSON могла быть другой).

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 {}

Ответ 2

Для этого вы можете использовать Gson Fire. Код будет выглядеть примерно так:

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 default behavior
            }
        }
    });
Gson gson = builder.createGson();

Ответ 4

Вам не нужно писать собственный адаптер.

Наконец, gson имеет свой собственный RuntimeTypeAdapterFactory для обработки java-полиморфизма, но, к сожалению, он не является частью основной библиотеки gson (чтобы использовать его, вы должны скопировать и вставить класс в свой проект).

Предположим, что у вас есть этот Animal.class:

public class Animal {
    protected String name;
    protected String type;

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

И Horse.class:

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

Вы можете инициализировать RuntimeTypeAdapterFactory используя следующий код:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Horse.class, "horse")
.registerSubtype(Cow.class, "cow");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Здесь вы можете найти полный рабочий пример.