初老のボケ防止日記

おっさんのひとりごとだから気にしないようにな。

GsonでJSON文字列をGenericsなオブジェクトに変換するときにハマる

GsonのGはガンダムの「G」です(違


JavaでJSONを扱うときにいつもお世話になっております。

github.com

で、タイトル通り、ハマったのでメモ。

変換対象のクラス

こんな感じで共通部以外をジェネリクスでやっちゃうぜ的なイメージ。

    class Hoge<T> {
        String name;
        int    id;
        T      value;

        @Override
        public String toString() {
            return "Hoge{" +
                    "name='" + name + '\'' +
                    ", id=" + id +
                    ", value=" + value +
                    '}';
        }
    }

    class Fuga {
        String name;
        int    id;

        public Fuga(String name, int id) {
            this.name = name;
            this.id = id;
        }

        @Override
        public String toString() {
            return "Fuga{" +
                    "name='" + name + '\'' +
                    ", id=" + id +
                    '}';
        }
    }

GenericsなオブジェクトからJSONへの変換

JSONへの変換はこんな感じで何も考えずに行ける

import com.google.gson.Gson;

public class GsonTest {
   public static void main( String ... args){

        Hoge<Fuga> hoge = new Hoge<Fuga>();
        hoge.name = "aaaa";
        hoge.id   = 3;
        Fuga fuga = new Fuga("bbbb",1000);
        hoge.value = fuga;
        System.out.println(String.format("Object->[%s]", hoge));
    }
}

実行するとこうなる。

Object->[Hoge{name='aaaa', id=3, value=Fuga{name='bbbb', id=1000}}]
JSON->[{"name":"aaaa","id":3,"value":{"name":"bbbb","id":1000}}]

JSONからオブジェクトへの変換

「Gson User Guide」にやり方が書いてある。

sites.google.com

親切だ。

サンプルどおりの手順

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class GsonTest {
   public static void main( String ... args){
        // 上で作ったjsonをオブジェクトに変換
        
        hoge = decode(json);
        System.out.println(String.format("Object(Hoge)->[%s]", hoge));
        fuga = hoge.value;
        System.out.println(String.format("Object(Fuga)->[%s]", fuga));
    }
    
   private static Hoge<Fuga> decode( String json ){
        Gson gson = new Gson();
        Type type = new TypeToken<Hoge<Fuga>>(){}.getType();
        System.out.println(type.getTypeName());
        return  gson.fromJson(json, type);
    }
}

実行するとこうなる。

Main$Hoge<Main$Fuga>
Object(Hoge)->[Hoge{name='aaaa', id=3, value=Fuga{name='bbbb', id=1000}}]
Object(Fuga)->[Fuga{name='bbbb', id=1000}]

汎用化してみる

実際はHogeに色んなクラスを入れるのでdecodeを汎用化しよう

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class GsonTest {
   public static void main( String ... args){
        // 上で作ったjsonをオブジェクトに変換
        
        hoge = decode(json, Fuga.class);
        System.out.println(String.format("Object(Hoge)->[%s]", hoge));
        fuga = hoge.value;
        System.out.println(String.format("Object(Fuga)->[%s]", fuga));
    }
    private static <T> Hoge<T> decode( String json, Class<T> tClass ) {
        Gson gson = new Gson();
        Type type = new TypeToken<Hoge<T>>(){}.getType();
        System.out.println(type.getTypeName());
        return gson.fromJson(json, type);
    }    
}

実行するとこうなる。

Main$Hoge<T>
Object(Hoge)->[Hoge{name='aaaa', id=3, value={name=bbbb, id=1000.0}}]
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to Main$Fuga
	at Main.main(Main.java:xx)

GsonのfromJsonの結果はHogeクラスまではデコードできているんだけど、valueがFugaクラスではなくて「com.google.gson.internal.LinkedTreeMap」になっている。

困った…

ということで、StackOverFlowさん出番です。

stackoverflow.com

Once your .java source file is compiled, the type parameter is obviously thrown away. Since it is not known at compile time, it cannot be stored in bytecode, so it's erased and Gson can't read it.

要するに

"<T>"はビルド時に消えるので動的には見えねよバーカバーカ。だから親切心でLinkedTreeMapにしといてやったわー。

ってことらしい。
ということで上記の回答にあったような、ParameterizedTypeを実装したジェネリクスの情報を保持するクラスを作ればよい。

    class GenericOf<X, Y> implements ParameterizedType {

        private final Class<X> container;
        private final Class<Y> wrapped;

        public GenericOf(Class<X> container, Class<Y> wrapped) {
            this.container = container;
            this.wrapped = wrapped;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{wrapped};
        }

        @Override
        public Type getRawType() {
            return container;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }
import com.google.gson.Gson;

import java.lang.reflect.ParameterizedType;

public class GsonTest {
   public static void main( String ... args){
        // 上で作ったjsonをオブジェクトに変換
        
        hoge = decode(json, Fuga.class);
        System.out.println(String.format("Object(Hoge)->[%s]", hoge));
        fuga = hoge.value;
        System.out.println(String.format("Object(Fuga)->[%s]", fuga));
    }
    private static <T> Hoge<T> decode( String json, Class<T> tClass ) {
        Gson gson = new Gson();
        ParameterizedType type = new GenericOf(Hoge.class,tClass);
        System.out.println(type.getRawType());
        System.out.println(type.getActualTypeArguments()[0]);
        return gson.fromJson(json, type);
    }    

}

実行するとこうなる。

class Main$Hoge
class Main$Fuga
Object(Hoge)->[Hoge{name='aaaa', id=3, value=Fuga{name='bbbb', id=1000}}]
Object(Fuga)->[Fuga{name='bbbb', id=1000}]

CYBORGじいちゃんG 1 (集英社文庫 お 55-4)

CYBORGじいちゃんG 1 (集英社文庫 お 55-4)