メソッド内に定義されたロカールクラスで Gson().fromJson できない原因を調査してみた
こんな感じのコードでLoader1
クラスを使ってJSONの文字列を読むことができるが Loader2
クラスを使っては読むことができない。
class Test { data class Loader1 (val type: String) fun test() { val jsonString = """{"type": "abc"}""" val ret1 = Gson().fromJson(jsonString, Loader1::class.java) assert(ret1.type == "abc") data class Loader2 (val type: String) val ret2 = Gson().fromJson(jsonString, Loader2::class.java) assert(ret2.type == "abc") // ret2 は null } }
デコンパイルして Java にしてみる
Show Kotlin bytecode
を押す
Decompile
を押す
Java に Decompile
されたコードが表示される。
以下抜粋した Java のコード
public final class Test { public final void test() { final class Loader2 { @NotNull private final String type; @NotNull public final String getType() { return this.type; } public Loader2(@NotNull String type) { this.type = type; } } Loader2 ret2 = (Loader2)(new Gson()).fromJson(jsonString, Loader2.class); } }
Java の local class が使われていることが確認できた。
続いて Gson が local class をどのように扱うのか調べる。
skipDeserialize
が true
なので null
を返す。
@Override public T read(JsonReader in) throws IOException { if (skipDeserialize) { in.skipValue(); return null; } return delegate().read(in); }
skipDeserialize
以下で excludeClass
が true
になっている。
boolean excludeClass = excludeClassChecks(rawType); final boolean skipSerialize = excludeClass || excludeClassInStrategy(rawType, true); final boolean skipDeserialize = excludeClass || excludeClassInStrategy(rawType, false);
excludeClass
に以下のコードでtrue
が代入されていた。
if (isAnonymousOrLocal(clazz)) { return true; }
isAnonymousOrLocal
の実装は以下のようになっておりLoader2.class.isLocalClass() == true
なので fromJson
できなかった。
private boolean isAnonymousOrLocal(Class<?> clazz) { return !Enum.class.isAssignableFrom(clazz) && (clazz.isAnonymousClass() || clazz.isLocalClass()); }
なぜGsonはAnonymousかLocalクラスを使ってロードできないのか調べていたら以下のissueにたどり着いた。
Don't use double brace initialization. It prevents serialization and Gson is designed for symmetric serialization and serialization. Original comment by limpbizkit on 7 Feb 2013 at 1:02 Changed state: WontFix
で、最終的にちゃんとユーザーガイドに書いてあることに気づいた。
Gson can also deserialize static nested classes. However, Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:
結論: ちゃんとドキュメントを読みましょう。