Gsonでjson文字列の逆シリアル化に問題があります。コマンドの配列を受け取ります。コマンドは、start、stop、その他のタイプのコマンドにすることができます。当然、ポリモーフィズムがあり、開始/停止コマンドはコマンドから継承します。
Gsonを使用して正しいコマンドオブジェクトにシリアル化するにはどうすればよいですか?
宣言された型であり、ランタイム型ではない基本型のみを取得しているようです。
これは少し遅れていますが、今日はまったく同じことをしなければなりませんでした。したがって、私の研究に基づいて、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には Type Hierarchy Adapterを登録する というメカニズムがありますが、これは単純なポリモーフィックデシリアライゼーション用に設定できると報告されていますが、Type Hierarchy Adapterが単に結合されているように見えるため、どのようになるかわかりませんシリアライザ/デシリアライザ/インスタンス作成者。インスタンス作成の詳細は、実際のポリモーフィック型登録を提供せずに、コーダーに任せます。
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);
}
}
マーカス・ジュニウス・ブルータスは素晴らしい答えを持っていました(ありがとう!)。彼の例を拡張するために、彼のアダプタークラスをジェネリックにして、次の変更を加えて、すべてのタイプのオブジェクト(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();
アダプターのシリアル化メソッドは、シリアル化するタイプのカスケード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を保持する各インスタンスに「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
を使用する限り、これはタイプアダプターを呼び出しません。
タイプのTypeAdapterとそのサブタイプの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を送信します
私はさまざまなユースケースのソリューションを説明していますが、無限再帰問題にも対処します
ケース1:あなたはクラスを制御しています、つまり、あなたは自分でCat
、Dog
を書くことができますクラスとIAnimal
インターフェイス。 @ marcus-junius-brutus(最高評価の回答)が提供するソリューションに従うだけです。
IAnimal
として共通の基本インターフェースがある場合、無限再帰はありません。
しかし、IAnimal
またはそのようなインターフェースを実装したくない場合はどうなりますか?
次に、@ marcus-junius-brutus(最高評価の回答)は無限再帰エラーを生成します。この場合、次のようなことができます。
次のように、基本クラスとラッパーサブクラス内にcopy constructorを作成する必要があります。
。
// 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;
}
}
それでは、なぜコピーコンストラクターなのでしょうか?
さて、コピーコンストラクターを定義すると、ベースクラスがいくら変更されても、ラッパーは同じ役割を継続します。次に、コピーコンストラクターを定義せず、基本クラスを単にサブクラス化する場合、拡張クラス、つまりCatWrapper
の観点から「話す」必要があります。コンポーネントがラッパー型ではなく、基本クラスの観点から話す可能性は十分にあります。
簡単な代替手段はありますか?
確かに、今ではGoogleによって導入されています-これはRuntimeTypeAdapterFactory
実装です:
RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory)
.create();
ここでは、Animal
に "type"というフィールドを導入し、同じ内部Dog
の値を "dog"に、Cat
を "cat"に導入する必要があります。
ケース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}}