回答:
これは少し遅いですが、今日はまったく同じことをしなければなりませんでした。したがって、私の研究に基づいており、gson-2.0を使用する場合は、registerTypeHierarchyAdapterメソッドを使用するのではなく、より平凡なregisterTypeAdapterを使用する必要があります。そしてもちろん、instanceofsを実行したり、派生クラスのアダプターを作成したりする必要はありません。もちろん、派生クラスのデフォルトのシリアル化に問題がなければ、基本クラスまたはインターフェイス用のアダプターは1つだけです。とにかく、これがコードです(パッケージとインポートは削除されました)(githubでも利用可能):
基本クラス(私の場合はインターフェース):
public interface IAnimal { public String sound(); }
2つの派生クラス、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 + ")";
}
}
IAnimalAdapter:
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);
}
}
そして、Testクラス:
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シリアライザー/デシリアライザークラスを実装する必要があるように見えました。
Gsonには現在、タイプ階層アダプターを登録するメカニズムがあります。これは、単純なポリモーフィックな逆シリアル化用に構成できると報告されていますが、タイプ階層アダプターがシリアライザ/デシリアライザ/インスタンスの作成者の組み合わせのように見えるため、そのように見えません。実際のポリモーフィック型の登録を提供せずに、インスタンス作成の詳細をコーダーに任せます。
GsonはまもなくRuntimeTypeAdapter
、より単純なポリモーフィックな逆シリアル化を行う予定です。詳細については、http://code.google.com/p/google-gson/issues/detail?id = 231を参照してください。
新しいものを使用RuntimeTypeAdapter
できず、Gsonを使用する必要がある場合は、独自のソリューションをロールバックして、カスタムデシリアライザーをタイプ階層アダプターまたはタイプアダプターとして登録する必要があると思います。以下はその一例です。
// 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);
}
}
RuntimeTypeAdapter
完全になりましたが、残念ながらGsonコアにはまだ含まれていません。:-(
マーカス・ジュニウス・ブルータスは素晴らしい答えを出しました(ありがとう!)。彼の例を拡張するには、次の変更を加えて、アダプタクラスをジェネリックにして、すべてのタイプのオブジェクト(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には、タイプ階層アダプターを定義および登録する方法を示すかなり良いテストケースがあります。
これを使用するには、次のようにします。
gson = new GsonBuilder()
.registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
.create();
アダプターのSerializeメソッドは、シリアル化するタイプのカスケード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;
逆シリアル化は少しハッキーです。単体テストの例では、Tell-Tale属性の存在をチェックして、デシリアライズするクラスを決定します。シリアル化するオブジェクトのソースを変更できる場合は、インスタンスクラスの名前のFQNを保持する各インスタンスに 'classType'属性を追加できます。ただし、これは非常に非オブジェクト指向です。
Googleは独自のRuntimeTypeAdapterFactoryをリリースしました、ポリモーフィズムを処理するためのをが、残念ながらそれは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();
}
}
ここに無限の再帰がない理由は、実際のランタイムクラス(つまり、IAnimalではなくDog)をに渡すためcontext.deserialize
です。これは、使用registerTypeAdapter
しない限り、タイプアダプタを呼び出しません。registerTypeHierarchyAdapter
私はさまざまなユースケースの解決策を説明しており、無限再帰問題にも対処します
ケース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;
}
}
では、なぜコピーコンストラクタなのでしょうか。
まあ、コピーコンストラクターを定義すると、基本クラスがどれだけ変化しても、ラッパーは同じ役割を継続します。第2に、コピーコンストラクタを定義せずに基本クラスを単にサブクラス化する場合は、拡張クラスの観点から「対話」する必要があり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
「dog」にCat
して「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);
}
}
したがって、CatWrapper
代わりにCat
、DogWrapper
代わりにDog
、
AlternativeAnimalAdapter
代わりにIAnimalAdapter
// 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}}
タイプのTypeAdapterとそのサブタイプのタイプを管理する場合は、次のようにTypeAdapterFactoryを使用できます。
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の回答とuser2242263の編集を組み合わせると、インターフェースタイプで動作するようにアダプターを定義することで、アダプターに大きなクラス階層を指定する必要がなくなります。次に、インターフェースでtoJSON()およびfromJSON()のデフォルト実装を提供し(これらの2つのメソッドのみが含まれます)、シリアル化する必要があるすべてのクラスにインターフェースを実装させることができます。キャストを処理するために、サブクラスで、インターフェイスタイプから適切なキャストを逆シリアル化して実行する静的なfromJSON()メソッドを提供できます。これは私にとってはうまくいきました(ハッシュマップを含むクラスのシリアライズ/デシリアライズに注意してください-gsonビルダーをインスタンス化するときにこれを追加してください:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
これが誰かが時間と労力を節約するのに役立つことを願っています!