programing

gson을 사용한 다형성

randomtip 2022. 9. 6. 21:58
반응형

gson을 사용한 다형성

Gson과의 json 문자열을 역직렬화하는 데 문제가 있습니다.명령어 배열이 수신됩니다.명령어는 start, stop, 기타 유형의 명령어입니다.당연히 저는 다형성을 가지고 있고 start/stop 명령어는 명령어에서 상속됩니다.

gson을 사용하여 올바른 명령어개체로 시리얼화하려면 어떻게 해야 하나요?

선언된 유형인 기본 유형만 가져오고 런타임 유형은 가져오지 않는 것 같습니다.

조금 늦었지만 오늘도 똑같은 일을 해야 했어요.그래서 제가 조사한 바로는 gson-2.0을 사용할 때는 registerTypeHierarchyAdapter 메서드를 사용하는 것이 아니라 일반적인 레지스터를 사용하는 것이 좋습니다.타입 어댑터파생 클래스에 대해 인스턴스화 또는 어댑터 쓰기를 수행할 필요가 없습니다.기본 클래스 또는 인터페이스용 어댑터는 1개뿐입니다.단, 파생 클래스의 디폴트 시리얼화에 만족하는 경우입니다.어쨌든, 코드는 다음과 같습니다(패키지 및 Import 삭제). (github에서도 사용 가능)

베이스 클래스(이 경우는 인터페이스):

public interface IAnimal { public String sound(); }

파생 클래스 Cat:

public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}

그리고 개:

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}

IAnimal 어댑터:

public class IAnimalAdapter implements JsonSerializer<IAnimal>, JsonDeserializer<IAnimal>{

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IAnimal src, Type typeOfSrc,
            JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public IAnimal deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

테스트 클래스:

public class Test {

    public static void main(String[] args) {
        IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, IAnimal.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class);
            System.out.println(animal2.sound());
        }
    }
}

Test::main을 실행하면 다음과 같은 출력이 표시됩니다.

serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}}
Kitty : "meaow"
serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}}
Brutus : "bark" (ferocity level:5)

저는 실제로 registerTypeHierarchyAdapter 메서드를 사용하여 위의 작업을 수행했지만, 이를 위해서는 커스텀 DogAdapter 및 CatAdapter serializer/deserializer 클래스를 구현해야 하는 것 같습니다.이러한 클래스는 Dog 또는 Cat에 다른 필드를 추가할 때마다 유지하기가 어렵습니다.

Gson은 현재 타입 계층 어댑터를 등록하는 메커니즘을 가지고 있습니다.이러한 메커니즘은 단순한 다형성 디시리얼라이제이션용으로 설정할 수 있다고 알려져 있습니다만, 타입 계층 어댑터는 단순히 조합된 시리얼라이저/디시리얼라이저/인스턴스 크리에이터로서 인스턴스 작성에 대한 자세한 내용은 코더에 남겨져 있습니다.실제 다형성 유형 등록을 딩합니다.

이 Gson을 될 것 같습니다.RuntimeTypeAdapter단순한 다형성 탈직렬화를 위해.상세한 것에 대하여는, http://code.google.com/p/google-gson/issues/detail?id=231 를 참조해 주세요.

「」를 사용하고 .RuntimeTypeAdapter고손그러면 사용자 지정 디시리얼라이저를 타입 계층 어댑터 또는 타입 어댑터로 등록하여 독자적인 솔루션을 롤링해야 합니다.을 사용하다

// output:
//     Starting machine1
//     Stopping machine2

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.FieldNamingPolicy;
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;

public class Foo
{
  // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}]
  static String jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    CommandDeserializer deserializer = new CommandDeserializer("command");
    deserializer.registerCommand("start", Start.class);
    deserializer.registerCommand("stop", Stop.class);
    gsonBuilder.registerTypeAdapter(Command.class, deserializer);
    Gson gson = gsonBuilder.create();
    Command[] commands = gson.fromJson(jsonInput, Command[].class);
    for (Command command : commands)
    {
      command.execute();
    }
  }
}

class CommandDeserializer implements JsonDeserializer<Command>
{
  String commandElementName;
  Gson gson;
  Map<String, Class<? extends Command>> commandRegistry;

  CommandDeserializer(String commandElementName)
  {
    this.commandElementName = commandElementName;
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gson = gsonBuilder.create();
    commandRegistry = new HashMap<String, Class<? extends Command>>();
  }

  void registerCommand(String command, Class<? extends Command> commandInstanceClass)
  {
    commandRegistry.put(command, commandInstanceClass);
  }

  @Override
  public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    try
    {
      JsonObject commandObject = json.getAsJsonObject();
      JsonElement commandTypeElement = commandObject.get(commandElementName);
      Class<? extends Command> commandInstanceClass = commandRegistry.get(commandTypeElement.getAsString());
      Command command = gson.fromJson(json, commandInstanceClass);
      return command;
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
}

abstract class Command
{
  String machineName;

  Command(String machineName)
  {
    this.machineName = machineName;
  }

  abstract void execute();
}

class Stop extends Command
{
  Stop(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Stopping " + machineName);
  }
}

class Start extends Command
{
  Start(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Starting " + machineName);
  }
}

Marcus Junius Brutus는 훌륭한 대답을 했다.예를 확장하려면 어댑터 클래스를 다음과 같은 변경으로 모든 유형의 개체(IAnimal뿐 아니라)에 대해 작동하도록 일반화할 수 있습니다.

class InheritanceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T>
{
....
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
....
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
....
}

그리고 테스트 클래스:

public class Test {
    public static void main(String[] args) {
        ....
            builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>());
        ....
}

GSON에는 유형 계층 어댑터를 정의하고 등록하는 방법을 보여주는 매우 좋은 테스트 사례가 있습니다.

http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739

이것을 사용하려면 , 다음과 같이 하십시오.

    gson = new GsonBuilder()
          .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
          .create();

어댑터의 시리얼화 방법은 시리얼화하는 타입의 캐스케이드 if-else 체크입니다.

    JsonElement result = new JsonObject();

    if (src instanceof SliderQuestion) {
        result = context.serialize(src, SliderQuestion.class);
    }
    else if (src instanceof TextQuestion) {
        result = context.serialize(src, TextQuestion.class);
    }
    else if (src instanceof ChoiceQuestion) {
        result = context.serialize(src, ChoiceQuestion.class);
    }

    return result;

역직렬화는 좀 허술하다.유닛 테스트의 예에서는, 텔테일 속성의 존재를 체크하고, 역직렬화할 클래스를 결정합니다.직렬화할 개체의 소스를 변경할 수 있는 경우 ' 클래스를 추가할 수 있습니다.인스턴스 클래스 이름의 FQN을 유지하는 각 인스턴스에 대한 Type' 속성.하지만 이것은 매우 객관적이지 않다.

구글이 자체 런타임을 출시했습니다.다형성을 처리하기 위해 Adapter Factory 라고 입력합니다만, 유감스럽게도 gson 코어의 일부가 아닙니다(프로젝트내에 클래스를 카피앤 페이스트 할 필요가 있습니다).

예:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

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

애니멀, 도그, 캣 모델을 사용한 완전한 작업 예를 게재했습니다.

처음부터 다시 구현하기보다는 이 어댑터에 의존하는 것이 좋다고 생각합니다.

오랜 시간이 흘렀지만 온라인에서 정말 좋은 해결책을 찾을 수 없었다.무한 재귀를 회피하는 @MarcusJuniusBrutus 솔루션의 작은 반전을 다음에 나타냅니다.

동일한 시리얼라이저를 유지하되 시리얼라이저를 분리합니다.

public class IAnimalAdapter implements JsonDeSerializer<IAnimal> {
  private static final String CLASSNAME = "CLASSNAME";
  private static final String INSTANCE  = "INSTANCE";

  @Override
  public IAnimal deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException  {
    JsonObject jsonObject =  json.getAsJsonObject();
    JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
    String className = prim.getAsString();

    Class<?> klass = null;
    try {
        klass = Class.forName(className);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new JsonParseException(e.getMessage());
    }
    return context.deserialize(jsonObject.get(INSTANCE), klass);
  }
}

다음 에 '하다'라는 합니다.@SerializedName("CLASSNAME")이제 기본 클래스의 컨스트럭터로 초기화하는 것이 요령입니다.그러므로 인터페이스를 추상 클래스로 만듭니다.

public abstract class IAnimal {
  @SerializedName("CLASSNAME")
  public String className;

  public IAnimal(...) {
    ...
    className = this.getClass().getName();
  }
}

Dog를 "Dog"로 입니다.context.deserialize를 사용하고 있는 한, 타입 어댑터는 호출되지 않습니다.registerTypeAdapter andregisterTypeHierarchyAdapter

갱신된 답변 - 기타 모든 답변의 장점

다양한 사용 사례에 대한 솔루션을 설명하고 무한 재귀 문제에 대해서도 설명합니다.

  • 케이스 1: 클래스를 관리하고 있습니다.즉, 직접 작성하실 수 있습니다.Cat,Dog클래스뿐만 아니라IAnimal인터페이스입니다.@marcus-junius-brutus(최상위 등급 답변)에서 제공하는 솔루션을 따를 수 있습니다.

    다음과 같은 공통 기본 인터페이스가 있는 경우 무한 재귀는 발생하지 않습니다.IAnimal

    단, 이 기능을 실장하지 않을 수 있습니다.IAnimal또는 그런 인터페이스가 있나요?

    그 후 @marcus-junius-brutus(최상위 등급 응답)에 의해 무한 재귀 오류가 발생합니다.이 경우 다음과 같은 작업을 할 수 있습니다.

    다음과 같이 기본 클래스 및 래퍼 서브 클래스 내에 복사 생성자를 만들어야 합니다.

.

// Base class(modified)
public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

그리고 그 타입의 시리얼라이저는Cat:

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.


        return modifyJSON(catWrapped);
    }

    private JsonElement modifyJSON(JsonElement base){
        // TODO: Modify something
        return base;
    }
}

그럼 왜 복사 제작자일까요?

일단 복사 생성자를 정의하면 기본 클래스가 아무리 변경되더라도 래퍼는 동일한 역할로 계속됩니다.둘째, copy constructor를 정의하지 않고 단순히 base 클래스를 서브클래스로 하는 경우 확장 클래스의 관점에서 "대화"해야 합니다.CatWrapper컴포넌트가 래퍼 타입이 아닌 베이스 클래스의 관점에서 이야기할 가능성이 있습니다.

쉬운 대안은 없나요?

네, 구글에 의해 도입되었습니다.이것은RuntimeTypeAdapterFactory구현:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

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

여기에서는, 「type」이라고 하는 필드를 도입할 필요가 있습니다.Animal그리고 같은 내면의 가치는Dog'개'가 되기 위해서Cat'고양이 되다

완전한 예: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html

  • 케이스 2: 클래스를 제어할 수 없습니다.이미 정의된 클래스가 있는 회사에 가입하거나 라이브러리를 사용하는 경우 매니저는 클래스를 변경하지 않도록 할 수 있습니다.클래스를 서브클래스로 분류하여 다음과 같은 공통 마커 인터페이스(메서드 없음)를 구현할 수 있습니다.AnimalInterface.

    예:

.

// The class we are NOT allowed to modify

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}


// The marker interface

public interface AnimalInterface {
}

// The subclass for serialization

public class DogWrapper  extends Dog implements AnimalInterface{

    public DogWrapper(String name, int ferocity) {
        super(name, ferocity);
    }

}

// The subclass for serialization

public class CatWrapper extends Cat implements AnimalInterface{


    public CatWrapper(String name) {
        super(name);
    }
}

그래서 우리는 이 두 가지를CatWrapperCat,DogWrapperDog ★★★★★★★★★★★★★★★★★」AlternativeAnimalAdapterIAnimalAdapter

// The only difference between `IAnimalAdapter` and `AlternativeAnimalAdapter` is that of the interface, i.e, `AnimalInterface` instead of `IAnimal`

public class AlternativeAnimalAdapter implements JsonSerializer<AnimalInterface>, JsonDeserializer<AnimalInterface> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(AnimalInterface src, Type typeOfSrc,
                                 JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public AnimalInterface deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

테스트를 실시합니다.

public class Test {

    public static void main(String[] args) {

        // Note that we are using the extended classes instead of the base ones
        IAnimal animals[] = new IAnimal[]{new CatWrapper("Kitty"), new DogWrapper("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(AnimalInterface.class, new AlternativeAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, AnimalInterface.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            AnimalInterface animal2 = gsonExt.fromJson(animalJson, AnimalInterface.class);
        }
    }
}

출력:

serialized with the custom serializer:{"CLASSNAME":"com.examples_so.CatWrapper","INSTANCE":{"name":"Kitty"}}
serialized with the custom serializer:{"CLASSNAME":"com.examples_so.DogWrapper","INSTANCE":{"name":"Brutus","ferocity":5}}

타입 어댑터와 서브 타입을 관리하는 경우는, 다음과 같이 타입 어댑터 팩토리를 사용할 수 있습니다.

public class InheritanceTypeAdapterFactory implements TypeAdapterFactory {

    private Map<Class<?>, TypeAdapter<?>> adapters = new LinkedHashMap<>();

    {
        adapters.put(Animal.class, new AnimalTypeAdapter());
        adapters.put(Dog.class, new DogTypeAdapter());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        TypeAdapter<T> typeAdapter = null;
        Class<?> currentType = Object.class;
        for (Class<?> type : adapters.keySet()) {
            if (type.isAssignableFrom(typeToken.getRawType())) {
                if (currentType.isAssignableFrom(type)) {
                    currentType = type;
                    typeAdapter = (TypeAdapter<T>)adapters.get(type);
                }
            }
        }
        return typeAdapter;
    }
}

이 공장에서는 가장 정확한 TypeAdapter를 발송합니다.

Marcus Junius Brutus의 답변과 사용자 2242263의 편집을 조합하면 어댑터가 인터페이스 유형으로 동작하는 것으로 정의되어 어댑터에 대규모 클래스 계층을 지정할 필요가 없습니다.다음으로 인터페이스에 toJSON()과 fromJSON()의 디폴트 실장을 제공하고(이러한 2개의 메서드만 포함), 시리얼라이즈 할 필요가 있는 모든 클래스에서 인터페이스를 실장할 수 있습니다.캐스팅을 처리하려면 서브클래스에서 인터페이스 유형에서 적절한 캐스팅을 역직렬화하고 실행하는 스태틱 from JSON() 메서드를 제공할 수 있습니다.이것은 나에게 있어서 매우 효과가 있었습니다(해시맵을 포함한 클래스의 시리얼화/디시리얼화에 주의해 주세요).gson Builder를 인스턴스화할 때 이 항목을 추가합니다.

GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();

이것이 누군가 시간과 노력을 절약하는 데 도움이 되기를 바랍니다!

언급URL : https://stackoverflow.com/questions/5800433/polymorphism-with-gson

반응형