私はあなたが上記の話をされた正確な例外を再現することができませんでしたが、具体的なタイプK
とV
情報がここに存在しないので、私が考えていた最初の事はnew TypeToken<PairGeneric<K,V>>() {}.getType()
だった - 彼らはただのプレースホルダこの場所である 。しかし、この場合でも、型トークン(実質的に匿名クラス)の汎用パラメータ化は消去されず、実行時に読み取ることができます。デバッガでこのタイプのトークンの状態を見ると、具体的な型(パーマメータの型名はK
とV
)がありませんが、GSONは解決できる型の逆シリアル化戦略を知っている(実行時に読み込むことができるので、マップがパラメータ化されている場合)。
は、以下の一般的なPair
クラスを考えてみましょう:
final class Pair<K, V> {
@SerializedName("Key")
private final K key = null;
@SerializedName("Value")
private final V value = null;
private Pair() { }
K getKey() { return key; }
V getValue() { return value; }
}
GSONは、このように命名規則Javaでのフィールド名を維持するためにさせる、名前を上書きできますSerializedName
注釈をサポートしています。これは着信DTOクラスなので、コード自体がインスタンス化されることはないと想定されており、パブリックコンストラクタは提供されていません。 GSONはコンストラクタやアクセサもなくても動作するので、このクラスは純粋に読み取り専用にすることができます。
一般的な辞書JSONデシリアライザ
デシリアライザは、以下の具体的な種類を意識することなく、デシリアライズすることができます。主な違いは、GSONがPair
クラスの戦略を選択できることです。私が理解するところでは、GSONはDTOターゲットタイプに基づいて適切なデシリアライザを解決しようとします(レスポンスオブジェクトはマップmap1
とmap2
のパラメータ化されています)。これらの具体的な型はGSONによってさらに処理されます。
final class GenericDictionaryJsonDeserializer<K, V>
implements JsonDeserializer<Map<K, V>> {
private static final JsonDeserializer<Map<Object, Object>> dictionaryJsonDeserializer = new GenericDictionaryJsonDeserializer<>();
private GenericDictionaryJsonDeserializer() {
}
static <K, V> JsonDeserializer<Map<K, V>> getGenericDictionaryJsonDeserializer() {
@SuppressWarnings({ "unchecked", "rawtypes" })
final JsonDeserializer<Map<K, V>> castDictionaryJsonDeserializer = (JsonDeserializer) dictionaryJsonDeserializer;
return castDictionaryJsonDeserializer;
}
@Override
public Map<K, V> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) {
final JsonArray array = json.getAsJsonArray();
final Map<K, V> result = new LinkedHashMap<>();
for (int i = 0; i < array.size(); i++) {
final Pair<K, V> pair = context.deserialize(array.get(i), Pair.class);
result.put(pair.getKey(), pair.getValue());
}
return result;
}
}
あなたGson
インスタンスに必要なのはMap.class
と、一般的な辞書JSONのデシリアライザが結合されます。いくつかのマップを「Javaスタイル」でデシリアライズしたい場合や、デシリアライザクラスを改良したい場合は、矛盾を避けるため、Map
(interface IDictionary<K,V> extends Map<K,V>
という)以外の別のクラスに移行することを検討してください。
ターゲット辞書JSONデシリアライザ
あなたが現在避けるためにしようとしているターゲットのクラスと細かいいる例は、あなたが特定の型にバインドされます代替を使用する場合があります場合。
final class TargetedDictionaryJsonDeserializer<K, V>
implements JsonDeserializer<Map<K, V>> {
private final Type pairType;
private TargetedDictionaryJsonDeserializer(final Type pairType) {
this.pairType = pairType;
}
static <K, V> JsonDeserializer<Map<K, V>> getTargetedDictionaryJsonDeserializer(final TypeToken<Pair<K, V>> typeToken) {
return new TargetedDictionaryJsonDeserializer<>(typeToken.getType());
}
@Override
public Map<K, V> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) {
final JsonArray array = json.getAsJsonArray();
final Map<K, V> result = new LinkedHashMap<>();
for (int i = 0; i < array.size(); i++) {
final Pair<K, V> pair = context.deserialize(array.get(i), pairType);
result.put(pair.getKey(), pair.getValue());
}
return result;
}
}
アダプタのこれらのタイプの違いは、後者は具体的なタイプのトークンを受け入れることである(つまり、完全に(下のデモを参照)、コールサイトであなたが宣言している)、およびにタイプトークンタイプを使用していますそれをコンテキストの逆シリアル化に委譲します。それはあなたがそれを必要とする場所で強力な型安全性を保証します。
デモンストレーション
private static final String JSON = "{" +
"'map1':[{'Key':'key1','Value':'value1'},{'Key':'key2','Value':'value2'}]," +
"'map2':[{'Key':'1','Value':'1.0'},{'Key':'2','Value':'2.0'}]" +
"}";
private static final TypeToken<Map<String, String>> stringToStringTypeToken = new TypeToken<Map<String, String>>() {
};
private static final TypeToken<Map<Integer, Float>> integerToFloatTypeToken = new TypeToken<Map<Integer, Float>>() {
};
private static final TypeToken<Pair<String, String>> stringToStringPairTypeToken = new TypeToken<Pair<String, String>>() {
};
private static final TypeToken<Pair<Integer, Float>> integerToFloatPairTypeToken = new TypeToken<Pair<Integer, Float>>() {
};
public static void main(final String... args) {
dump("by-targeted", getByTargetedTypeAdapter());
dump("by-generic", getByGenericTypeAdapter());
dump("by-mixed", getByMixTypeAdapter());
}
private static Response getByTargetedTypeAdapter() {
return new GsonBuilder()
.registerTypeAdapter(stringToStringTypeToken.getType(), getTargetedDictionaryJsonDeserializer(stringToStringPairTypeToken))
.registerTypeAdapter(integerToFloatTypeToken.getType(), getTargetedDictionaryJsonDeserializer(integerToFloatPairTypeToken))
.create()
.fromJson(JSON, Response.class);
}
private static Response getByGenericTypeAdapter() {
return new GsonBuilder()
.registerTypeAdapter(Map.class, getGenericDictionaryJsonDeserializer())
.create()
.fromJson(JSON, Response.class);
}
private static Response getByMixTypeAdapter() {
return new GsonBuilder()
.registerTypeAdapter(Map.class, getGenericDictionaryJsonDeserializer())
.registerTypeAdapter(stringToStringTypeToken.getType(), getTargetedDictionaryJsonDeserializer(stringToStringPairTypeToken))
.create()
.fromJson(JSON, Response.class);
}
private static final class Response {
private final Map<String, String> map1 = null;
private final Map<Integer, Float> map2 = null;
}
private static void dump(final String name, final Response response) {
out.print(">> ");
out.println(name);
out.print(" ");
out.println(response.map1);
out.print(" ");
out.println(response.map2);
}
"対象" モードが具体的なタイプのアダプタを(stringToStringPairTypeToken
とintegerToFloatPairTypeToken
がインスタンス化される方法を参照)を渡します。 GsonBuilder
はオーバーライドが可能であるため、「混在」テストケースでは、一般的なテスト戦略の後にターゲット戦略が指定されます(そうでない場合、汎用戦略が常に使用されます)。
すべてのテストケースは、JSONを解析し、次の出力印刷:
{KEY1 =値1、値2 = KEY2}
{1 = 1.0、2 = 2.0}
を