いろいろガンガンいこうぜ。体も大事に

普段はJavaでAndroidアプリ開発しているプログラマーのブログです。

Gsonのユーザーガイドを見てみた (3)

前回,前々回に引き続き,Gsonのユーザーガイドを見ていきます。



Person.java

package com.rmstar.gson.userguide;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("%s %d才", name, age);
    }
}

サンプルとして,このクラスを使います。





JSONの表示フォーマット.単純な形式と分かりやすい形式
package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.ArrayList;
import java.util.List;

public class PrettyPrinting {
    public static void main(String[] args) {
        Gson gson;

        Person person = new Person("Alice", 25);

        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Taro", 20));
        personList.add(new Person("Jiro", 17));
        personList.add(new Person("Saburo", 14));

        System.out.println("===Compact===");
        gson = new Gson();
        System.out.println(gson.toJson(person));
        System.out.println(gson.toJson(personList));

        System.out.println();

        System.out.println("===Pretty Printing===");
        gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(person));
        System.out.println(gson.toJson(personList));
    }
}

実行結果は,

===Compact===
{"name":"Alice","age":25}
[{"name":"Taro","age":20},{"name":"Jiro","age":17},{"name":"Saburo","age":14}]

===Pretty Printing===
{
  "name": "Alice",
  "age": 25
}
[
  {
    "name": "Taro",
    "age": 20
  },
  {
    "name": "Jiro",
    "age": 17
  },
  {
    "name": "Saburo",
    "age": 14
  }
]


となります。

new Gson().toJson(object)

として生成したGsonでの出力文字列は,空白も改行もありません。

new GsonBuilder().setPrettyPrinting().create().toJson(object)

一方で,GsonBuilderを用いてGsonクラスのインスタンスを生成し,
setPrettyPrinting()を行った後にGson型のインスタンスを生成すれば,
上記のように空白が挿入されたり改行付きで表示されたりします。



nullオブジェクトのサポート
package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class NullObjectSupport {
    public static void main(String[] args) {

        Person person = new Person(null, 20);
        Gson gson;

        gson = new Gson();
        System.out.println(gson.toJson(person));

        gson = new GsonBuilder().serializeNulls().create();
        System.out.println(gson.toJson(person));
    }
}

実行結果

{"age":20}
{"name":null,"age":20}

普通にGsonを用いた場合はnullのフィールドは無視されます。
nullを出力したい場合は,
Gsonクラスをインスタンス化する際,

new GsonBuilder().serializeNulls().create()

とすれば,nullは無視されなくなります。



バージョニング
package com.rmstar.gson.userguide;

import com.google.gson.annotations.Since;

public class VersionedObject {
    private final String v1;

    @Since(2)
    private final String v2;

    @Since(3)
    private final String v3;

    public VersionedObject() {
        v1 = "version1";
        v2 = "version2";
        v3 = "version3";
    }
}

各フィールドに@Sinceアノテーションを上記のように記述して,

package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class VersioningSupport {
    public static void main(String[] args) {
        Gson gson;
        VersionedObject object = new VersionedObject();

        gson = new Gson();
        System.out.println(gson.toJson(object));

        gson = new GsonBuilder().setVersion(1).create();
        System.out.println(gson.toJson(object));

        gson = new GsonBuilder().setVersion(2).create();
        System.out.println(gson.toJson(object));

        gson = new GsonBuilder().setVersion(3).create();
        System.out.println(gson.toJson(object));
    }
}

上記のように,gsonをインスタンス化すれば,

{"v1":"version1","v2":"version2","v3":"version3"}
{"v1":"version1"}
{"v1":"version1","v2":"version2"}
{"v1":"version1","v2":"version2","v3":"version3"}

実行結果はこのようになります。

@Sinceアノテーションを用いれば,
同じオブジェクト・コードでバージョンによる差異を上手に扱えるみたいです。

もしGsonのインスタンスにバージョンの設定がされていないのであれば,
バージョンに関わらず全てのフィールドがシリアライズ・デシリアライズされるようです。


@Sinceアノテーションはクラスに設定することもできるようです。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Since;

import java.util.ArrayList;
import java.util.List;

public class VersioningSupport {

    private static class V1 {
        final String v = "version1";
    }

    @Since(2)
    private static class V2 {
        final String v = "version2";
    }

    @Since(3)
    private static class V3 {
        final String v = "version3";
    }

    public static void main(String[] args) {
        Gson gson;

        List<Object> list = new ArrayList<Object>();
        list.add(new V1());
        list.add(new V2());
        list.add(new V3());

        System.out.println("===Version1===");
        gson = new GsonBuilder().setVersion(1).create();
        System.out.println(gson.toJson(list));

        System.out.println("===Version2===");
        gson = new GsonBuilder().setVersion(2).create();
        System.out.println(gson.toJson(list));

        System.out.println("===Version3===");
        gson = new GsonBuilder().setVersion(3).create();
        System.out.println(gson.toJson(list));
    }
}

実行結果は,

===Version1===
[{"v":"version1"},null,null]
===Version2===
[{"v":"version1"},{"v":"version2"},null]
===Version3===
[{"v":"version1"},{"v":"version2"},{"v":"version3"}]

となりました。





フィールドのネーミング
package com.rmstar.gson.userguide;

public class Animal {
    private final String mName;
    private final int mWeight;

    public Animal(String name, int weight) {
        mName = name;
        mWeight = weight;
    }

}

このようなクラスがあったとして,

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class JSONFieldNamingSupport {
    public static void main(String[] args) {
        Animal pochi = new Animal("Pochi", 35);
        System.out.println(new Gson().toJson(pochi));
    }
}

この様にGsonを使うと,

{"mName":"Pochi","mWeight":35}

こうなります。
命名規約などでフィールドにmをつけなくてはいけない場合,
Gsonでの出力にもmがついてしまいます。



Animalクラスを次の用に書き換えます。

package com.rmstar.gson.userguide;

import com.google.gson.annotations.SerializedName;

public class Animal {
    @SerializedName("name")
    private final String mName;

    @SerializedName("weight")
    private final int mWeight;

    public Animal(String name, int weight) {
        mName = name;
        mWeight = weight;
    }

}

実行結果は以下のようになりました。

{"name":"Pochi","weight":35}

クラスのフィールド名は元のままですが,GsonでのJSON形式の文字列はmを取ることが出来ました。



シリアライズ・デシリアライズでのフィールドの除外

Gsonではシリアライズ・デシリアライズにおいて,
クラス・フィールド・ある型のフィールドを除外するための多くの仕組みが準備されています。




まずは,修飾子単位での除外

シリアライズの対象は以下のようなクラス。

package com.rmstar.gson.userguide;

public class Example1 {
    private String v0;
    static String v1;
    volatile String v2;
    transient String v3;
    static final String v4;

    static {
        v1 = "static";
        v4 = "constant";
    }

    public Example1() {
        v0 = "private";
        v2 = "volatile";
        v3 = "transient";
    }
}

これを普通にGsonを用いてシリアライズした場合,

{"v0":"private","v2":"volatile"}

となります。
static修飾子とtransient修飾子がついたフィールドは,無視されます。

無視したい修飾子を設定したい場合だとか,
逆にstaticなフィールドを無視したくない場合は,
GsonBuilderでGsonインスタンスを作成する際,
以下のように無視する修飾子を設定することが可能です。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.lang.reflect.Modifier;

public class ExcludingFields1 {
    public static void main(String[] args) {
        Gson gson;
        Example1 ex1 = new Example1();

        System.out.println("===Exclude Private===");
        gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.PRIVATE)
                .create();
        System.out.println(gson.toJson(ex1));

        System.out.println("===Exclude Static===");
        gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.STATIC)
                .create();
        System.out.println(gson.toJson(ex1));

        System.out.println("===Exclude Volatile and Transient===");
        gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.VOLATILE | Modifier.TRANSIENT)
                .create();
        System.out.println(gson.toJson(ex1));

        System.out.println("===Exclude Constant===");
        gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC)
                .create();
        System.out.println(gson.toJson(ex1));
    }
}

実行結果は,以下の通りです。

===Exclude Private===
{"v1":"static","v2":"volatile","v3":"transient","v4":"constant"}
===Exclude Static===
{"v0":"private","v2":"volatile","v3":"transient"}
===Exclude Volatile and Transient===
{"v0":"private","v1":"static","v4":"constant"}
===Exclude Constant===
{"v0":"private","v2":"volatile","v3":"transient"}




次に,@Exposeアノテーションを用いての除外
@Exposeアノテーションフィールを用いれば,
フィールド単位でシリアライズ・デシリアライズするフィールドを指定出来ます。

Gsonのインスタンス化を

new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();

のように行った場合,
@Exposedアノテーションがついたフィールドのみ
シリアライズ・デシリアライズされます。

package com.rmstar.gson.userguide;

import com.google.gson.annotations.Expose;

public class Example2 {
    @Expose
    private final String expose;

    private final String notExpose;

    public Example2() {
        expose = "expose";
        notExpose = "not expose";
    }
}

このようなクラスをシリアライズします。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class ExcludingFields2 {
    public static void main(String[] args) {
        Gson gson;
        Example2 ex2 = new Example2();

        gson = new Gson();
        System.out.println(gson.toJson(ex2));

        gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create();
        System.out.println(gson.toJson(ex2));
    }
}


実行結果は,以下のようになります。

{"expose":"expose","notExpose":"not expose"}
{"expose":"expose"}




独自に定義するExclusion Strategyによる除外を紹介します。

package com.rmstar.gson.userguide;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;

public class MyExclusionStrategy implements ExclusionStrategy {
    private final Class<?> mTypeToSkip;

    public MyExclusionStrategy(Class<?> typeToSkip) {
        mTypeToSkip = typeToSkip;
    }

    @Override
    public boolean shouldSkipClass(Class<?> arg0) {
        return (arg0.equals(mTypeToSkip));
    }

    @Override
    public boolean shouldSkipField(FieldAttributes arg0) {
        return false;
    }
}

このようにExclusionStrategyインターフェースを実装したクラスを独自で定義して,

package com.rmstar.gson.userguide;

import com.google.gson.ExclusionStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class ExcludingFields3 {
    public static void main(String[] args) {
        Person person = new Person("Alice", 25);
        ExclusionStrategy strategy;
        Gson gson;

        strategy = new MyExclusionStrategy(String.class);
        gson = new GsonBuilder()
                .addSerializationExclusionStrategy(strategy)
                .create();
        System.out.println(gson.toJson(person));

        strategy = new MyExclusionStrategy(int.class);
        gson = new GsonBuilder()
                .addSerializationExclusionStrategy(strategy)
                .create();
        System.out.println(gson.toJson(person));
    }
}

実行結果は,以下の通りです。

{"age":25}
{"name":"Alice"}






これでも出来ない場合は,自分で細かくカスタマイズすることもできるようです。
それはまた次回にでも。



それでは,また会えることを祈りつつ。

Hello Widget! (2) AppWidgetProviderについて

Widgetを作る際に作ったAppWidgetProviderのサブクラス。
最小実装のWidgetを作った時は,メソッドを何も定義しないクラスを作りました。


AppWidgetProviderクラス。
BroadcastReceiverのサブクラス。


中でどんな複雑なことをしているのだろう?
と思いきや,ソースコード自体は以外とシンプルでした。

void onReceive(Context context,Intent intent)の中で,
飛んで来たIntentのActionに対応したメソッドを呼び出しているだけだでした。

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

というそれぞれのActionに対して,

  • void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
  • void onDeleted(Context context, int[] appWidgetIds)
  • void onEnabled(Context context)
  • void onDisabled(Context context)
  • void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)


というメソッドが,AppWidgetProviderのonReceiveメソッドの中で呼ばれていました。
appWidgetIdsなどの引数はIntentのExtrasから抽出していました。

ちなみに,AppWidgetProviderクラス自体のonUpdateメソッドなどは全く何もしていないメソッドでした。


アンドロイドデベロッパーのサイトによると,
そもそも,AppWidgetProviderクラスはアップウィジェットに関わるインテントのブロードキャストを扱うための便利クラスのようです。



それぞれのメソッドを自分なりにまとめてみました。


void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)

このメソッドは,アップウィジェットを更新するために呼ばれる。
間隔は,AppWidgetProviderInfoの中のupdatePeriodMillisで指定した間隔で呼ばれる。
また,アップウィジェットウィジェットを初期化した時にも呼ばれる。
そのためもし必要であれば,ビューを扱うイベントハンドラーを定義したりするべき。
ウィジェットの設定Activityを使う場合は,
アップウィジェットを初期化した時このメソッドが呼ばれないことに注意しないといけない。


void onDeleted(Context context, int[] appWidgetIds)

アップウィジェットがアップウィジェットホストから消されるごとに呼ばれる。


void onEnabled(Context context)

初めてアップウィジェットインスタンスが生成されたときに呼ばれる。
二つ同じ種類のアップウィジェットを置いたとしても一回しか呼ばれない。
データベースを初期化生成したリだとか,
全てのアップウィジェットインスタンスに対して一回やればいいという処理はここ。


・void onDisabled(Context context)

アップウィジェットの最後のインスタンスがアップウィジェットホストから消される時呼ばれる。
一時的なデータベースの削除など,onEnabledメソッドでやったことに対しての後処理はここでやるべきらしい。


・void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)

ウィジェットが初めて置かれた時と,サイズが変わったときに呼ばれる。
ウィジェットのサイズを取得できる。
(APIレベル16から)






ちなみに,

アップウィジェットを作成するために
AppWidgetProviderを継承せずに直接BroadCastReceiverを継承し,
それぞれのIntentのActionに応じた処理を記述して〜
という実装方法も可能らしいです。




それでは,また会えることを祈りつつ。

Gsonのユーザーガイドを見てみた (2)

前回に引き続き,Gsonのユーザーガイドを見ていきます。

配列

Gsonのユーザーガイドには,intとStringの配列の例がありました。

Person.java

package com.rmstar.gson.userguide;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("%s %d才", name, age);
    }
}

自分で定義したPersonクラスの配列も試してみました。



package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class ArrayExample {
    public static void main(String[] args) {
        Gson gson = new Gson();

        int[] ints = new int[] {
                1, 2, 3, 4, 5
        };
        String[] strings = new String[] {
                "abc", "def", "ghi"
        };
        Person[] persons = new Person[] {
                new Person("taro", 20),
                new Person("jiro", 17),
                new Person("saburo", 14),
        };

        System.out.println(gson.toJson(ints));
        System.out.println(gson.toJson(strings));
        System.out.println(gson.toJson(persons));

        String intsString = "[3,2,1,0]";
        String stringsString = "[\"AAA\",\"BBB\",\"CCC\"]";
        String personString = "[{\"name\":\"Alice\",\"age\":24},{\"name\":\"Bob\",\"age\":25}]";

        int[] deserializedInts = 
                gson.fromJson(intsString, int[].class);
        String[] deserializedStrings =
                gson.fromJson(stringsString, String[].class);
        Person[] deserializedPersons =
                gson.fromJson(personString, Person[].class);

        System.out.println("=== Deserialized Ints ===");
        for (int value : deserializedInts) {
            System.out.println(value);
        }

        System.out.println("=== Deserialized Strings ===");
        for (String value : deserializedStrings) {
            System.out.println(value);
        }

        System.out.println("=== Deserialized Persons ===");
        for (Person value : deserializedPersons) {
            System.out.println(value);
        }

    }
}


実行結果は,


[1,2,3,4,5]
["abc","def","ghi"]
[{"name":"taro","age":20},{"name":"jiro","age":17},{"name":"saburo","age":14}]
=== Deserialized Ints ===
3
2
1
0
=== Deserialized Strings ===
AAA
BBB
CCC
=== Deserialized Persons ===
Alice 24才
Bob 25才


となりました。



ついでに,多次元配列もやってみました。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class MultiArrayEamaple {
    public static void main(String[] args) {
        System.out.println("===Multidimensional Array===");
        int[][] multiArray = new int[][] {
                new int[3],
                new int[3],
                new int[3],
        };
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                multiArray[i][j] = i + 2 * j;
            }
        }
        for (int[] a : multiArray) {
            System.out.println(String.format("%d %d %d", a[0], a[1], a[2]));
        }

        System.out.println("===Serialize===");
        Gson gson = new Gson();
        String serialized = gson.toJson(multiArray);
        System.out.println(gson.toJson(multiArray));

        System.out.println("===Deserialize===");
        int[][] deserialzed = gson.fromJson(serialized, int[][].class);
        for (int[] a : deserialzed) {
            System.out.println(String.format("%d %d %d", a[0], a[1], a[2]));
        }
    }
}

実行結果は次の通りです。

===Multidimensional Array===
0 2 4
1 3 5
2 4 6
===Serialize===
[[0,2,4],[1,3,5],[2,4,6]]
===Deserialize===
0 2 4
1 3 5
2 4 6


多次元配列も可能なようですね。



コレクション

package com.rmstar.gson.userguide;

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

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class CollectionExample1 {
    public static void main(String[] args) {
        Gson gson = new Gson();

        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);

        System.out.println("=== Serialize ===");
        System.out.println(gson.toJson(list));

        System.out.println("=== Deserialize ===");
        String jsonString = "[0,10,100]";
        Type listType = new TypeToken<List<Integer>>() {
        }.getType();

        List<Integer> deserialized = 
                gson.fromJson(jsonString, listType);
        System.out.println(deserialized);
        System.out.println(deserialized.getClass().getName());
    }
}


実行結果は,

=== Serialize ===
[1,2,3]
=== Deserialize ===
[0, 10, 100]
java.util.ArrayList

となりました。

SetやMapでもやってみようと思います。

package com.rmstar.gson.userguide;

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

import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class CollectionExample2 {
    public static void main(String[] args) {
        Gson gson = new Gson();

        Set<Integer> set = new TreeSet<Integer>();
        set.add(1);
        set.add(2);
        set.add(3);

        Map<String, Integer> map = new TreeMap<String, Integer>();
        map.put("a", 1);
        map.put("b", 2);

        System.out.println("=== Serialize ===");
        System.out.println(gson.toJson(set));
        System.out.println(gson.toJson(map));

        System.out.println("=== Deserialize ===");
        String setString = "[0,10,100]";
        String mapString = "{\"X\":10,\"Y\":-10}";
        Type setType = new TypeToken<Set<Integer>>() {
        }.getType();
        Type mapType = new TypeToken<Map<String, Integer>>() {
        }.getType();

        Set<Integer> deserializedSet =
                gson.fromJson(setString, setType);
        System.out.println(deserializedSet);
        System.out.println(deserializedSet.getClass().getName());

        Map<String, Integer> deserializedMap =
                gson.fromJson(mapString, mapType);
        System.out.println(deserializedMap);
        System.out.println(deserializedMap.getClass().getName());
    }
}


実行結果は,

=== Serialize ===
[1,2,3]
{"a":1,"b":2}
=== Deserialize ===
[0, 10, 100]
java.util.LinkedHashSet
{X=10, Y=-10}
java.util.LinkedHashMap


でした。

Type mapType = new TypeToken<Map<String, Integer>>() {
}.getType();

のように,TypeToken型のインスタンスを作る際の型引数に

  • Listインターフェース型
  • Setインターフェース型
  • Mapインターフェース型

を指定した場合,それぞれ

が生成されました。


当然インターフェースでなく,
インスタンス化したいクラスを指定してのインスタンス化も出来るようです。

package com.rmstar.gson.userguide;

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

import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.TreeMap;

public class CollectionExample3 {
    public static void main(String[] args) {
        Gson gson = new Gson();

        String listString = "[1,2,1]";
        String setString = "[0,10,100]";
        String mapString = "{\"X\":10,\"Y\":-10}";

        Type listType = new TypeToken<LinkedList<Integer>>() {
        }.getType();
        Type setType = new TypeToken<HashSet<Integer>>() {
        }.getType();
        Type mapType = new TypeToken<TreeMap<String, Integer>>() {
        }.getType();

        Class<?> listClass =
                gson.fromJson(listString, listType).getClass();
        Class<?> setClass =
                gson.fromJson(setString, setType).getClass();
        Class<?> mapClass =
                gson.fromJson(mapString, mapType).getClass();

        System.out.println(listClass.getName());
        System.out.println(setClass.getName());
        System.out.println(mapClass.getName());
    }
}

実行結果は,

java.util.LinkedList
java.util.HashSet
java.util.TreeMap

です。





総称型のシリアライズとデシリアライズ

toJson(obj)メソッドを呼んだ時,
内部的にobj.getClass()メソッドを呼んでいるそうです。
シリアライズするフィールドの情報を得るためだそうです。
同じように,fromJsonメソッドの第二引数にもclass型を入れますよね。

非総称型のオブジェクトをデシリアライズする場合は
int.classのようなクラス型でうまくいくのですが,
総称型のオブジェクトの場合うまくいきません。
総称型の情報が無くなっているからというのが原因みたいです。

Fuga.java

package com.rmstar.gson.userguide;

public class Fuga {
    int fuga;

    @Override
    public String toString() {
        return String.valueOf(fuga);
    }
}

Hoge.java

package com.rmstar.gson.userguide;

public class Hoge<T> {
    T hoge;

    @Override
    public String toString() {
        return String.format("Hoge : %s", hoge.toString());
    }
}

GenericTypeExample.java

package com.rmstar.gson.userguide;

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

import java.lang.reflect.Type;

public class GenericTypeExample {
    public static void main(String[] args) {

        System.out.println("=== Define ===");
        Gson gson = new Gson();

        Hoge<Fuga> a = new Hoge<Fuga>();
        a.hoge = new Fuga();
        a.hoge.fuga = 1;
        System.out.println(a);

        System.out.println("=== Serealize ===");
        String jsonA = gson.toJson(a);
        System.out.println(jsonA);

        System.out.println("=== Deserealize ===");
        Hoge<Fuga> deserializedA;

        deserializedA = gson.fromJson(jsonA, Hoge.class);
        System.out.println(deserializedA);

        Type type = new TypeToken<Hoge<Fuga>>() {
        }.getType();
        deserializedA = gson.fromJson(jsonA, type);
        System.out.println(deserializedA);
    }
}


GenericTypeExample.javaの実行結果は,

=== Define ===
Hoge : 1
=== Serealize ===
{"hoge":{"fuga":1}}
=== Deserealize ===
Hoge : {fuga=1.0}
Hoge : 1

でした。

Hoge.classをfomJsonの引数に取った場合には,
正しくデシリアライズできていないみたいです。




今回はこの辺で。

それでは,また会えることを祈りつつ。

Gsonのユーザーガイドを見てみた (1)

Gson User Guideに載っているサンプルを実際にやってみたり,書いてあるポイントを自分なりにまとめてみました。



基本型

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class Primitives {
    public static void main(String[] args) {
        serialize();
        System.out.println();
        deserialize();
    }

    public static void serialize() {
        Gson gson = new Gson();

        System.out.println("===Serialize===");
        System.out.println(gson.toJson(1));
        System.out.println(gson.toJson("abcd"));
        System.out.println(gson.toJson(new Long(10)));
        int[] values = {
                1
        };
        System.out.println(gson.toJson(values));
    }

    public static void deserialize() {
        Gson gson = new Gson();

        int oneInt = gson.fromJson("1", int.class);
        Integer oneInteger = gson.fromJson("1", Integer.class);
        Boolean falseBoolean = gson.fromJson("false", Boolean.class);
        String str = gson.fromJson("\"abc\"", String.class);
        String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);

        System.out.println("===Deserialize===");
        System.out.println(oneInt);
        System.out.println(oneInteger);
        System.out.println(falseBoolean);
        System.out.println(str);
        System.out.println(anotherStr[0]);
        System.out.println(String.format("length : %d", anotherStr.length));
    }
}


実行結果は,


===Serialize===
1
"abcd"
10
[1]

===Deserialize===
1
1
false
abc
abc
length : 1

となりました。





オブジェクトの例

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class ObjectExamples {
    private static class SampleObject {
        private int value1 = 1;
        private String value2 = "abc";
        private transient int value3 = 3;

        public SampleObject() {
        }
    }

    public static void main(String[] args) {
        SampleObject sampleObject = new SampleObject();
        Gson gson = new Gson();
        String jsonString = gson.toJson(sampleObject);
        System.out.println(jsonString);

        SampleObject deserialized = gson.fromJson(jsonString, SampleObject.class);
    }
}

実行結果は,

{"value1":1,"value2":"abc"}


でした。

ポイントは

  • フィールドに基本型を使った場合は問題なし。
  • JSON形式への変換,JSON形式からの変換。どちらもフィールドにアノテーションをつける必要なし。そのクラスと親クラスの全てのフィールドが変換に含まれる。
  • transient修飾子がついた場合,JSON形式への変換,JSON形式からの変換どちらも無視される。
  • nullを正しく扱うことが出来る。
    • JSON形式の文字列への変換(シリアライゼーション)では,値がnullのフィールドは変換結果のJSON文字列中に含まれない。
    • JSON形式の文字列からの変換(デシリアライゼーション)では,文字列中に含まれていない要素に対応するフィールドはnullとなる。
  • フィールドがsynethticならば,シリアライゼーションでも,デシリアライゼーションでも無視される。
  • インナークラス中のアウタークラス,無名クラス,ローカルクラスに該当するフィールドは無視されて,シリアライゼーションにもデシリアライゼーションにも含まれない。


だそうです。




いろいろ試してみました。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class Ex1 {

    private static class A {
        private int a;

        A(int a) {
            this.a = a;
        }
    }

    private static class B extends A {
        private int b;

        B(int a, int b) {
            super(a);
            this.b = b;
        }
    }

    private static class C extends B {
        private int c;

        C(int a, int b, int c) {
            super(a, b);
            this.c = c;
        }
    }

    public static void main(String[] args) {
        C c = new C(1, 2, 3);
        Gson gson = new Gson();
        System.out.println(gson.toJson(c));
    }
}


実行結果は,

{"c":3,"b":2,"a":1}


親クラスのプライベートなフィールドもJSON形式に変換されるらしいです。



次にnullの取り扱い。

package com.rmstar.gson.userguide;

import com.google.gson.Gson;

public class Ex2 {

    private static class Sample {
        private String name;
        private String value;

        Sample(String name, String value) {
            this.name = name;
            this.value = value;
        }
    }

    public static void main(String[] args) {
        Gson gson = new Gson();

        Sample sampleA = new Sample("a", "v1");
        Sample sampleB = new Sample("b", null);

        System.out.println(gson.toJson(sampleA));
        System.out.println(gson.toJson(sampleB));

        String sampleCJsonString = "{\"name\":\"c\"}";
        Sample sampleC = gson.fromJson(sampleCJsonString, Sample.class);
        System.out.println(sampleC.name);
        System.out.println(sampleC.value);
    }
}

実行結果は,

{"name":"a","value":"v1"}
{"name":"b"}
c
null

となるようです。




ネストされたクラス(インナークラスを含む)

Gsonはとても簡単にstaticなネストされたクラスをシリアライズできるようです。
また,Gsonはstaticなネストされたクラスをデシリアライズすることも可能です。


しかし,Gsonは純粋なインナークラスは自動的にデシリアライズできません。
インナークラスの引数なしのコンストラクターは,そのインナークラスを保持しているオブジェクトの参照が必要で,それがデシリアライズの時に利用不可能だからだそうです。


インナークラスをstaticにするか,カスタマイズされたInstanceCreatorを作る必要があるようです。


InstanceCreatorを使ってみました。
A.java

package com.rmstar.gson.userguide;

public class A {
    public String a;

    class B {

        public String b;

        public B() {
            // No args constructor for B
        }
    }
}


InstanceCreatorForB.java

package com.rmstar.gson.userguide;

import com.google.gson.InstanceCreator;

import java.lang.reflect.Type;

public class InstanceCreatorForB implements InstanceCreator<A.B> {
    private final A a;

    public InstanceCreatorForB(A a) {
        this.a = a;
    }

    public A.B createInstance(Type type) {
        return a.new B();
    }
}


Ex3.java

package com.rmstar.gson.userguide;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Ex3 {

    public static void main(String[] args) {
        A instanceA = new A();

        InstanceCreatorForB creator =
                new InstanceCreatorForB(instanceA);

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(A.B.class, creator)
                .create();

        A.B instanceB = gson.fromJson("{\"b\":\"abc\"}", A.B.class);
        System.out.println(instanceB.b);
    }
}


ちょっと面倒ですね。





今回はこの変で。


それでは,また会えることを祈りつつ。

Gsonを使ってみた。(1)

Gsonを使ってみた。



Gson
JavaのオブジェクトをJSON形式に変換することができるライブラリ。
JSON文字列をJavaのオブジェクトに変換することもできるようです。


JavaオブジェクトをJSONに変換するオープンソースプロジェクトはいくつか存在するのだけれど次のような問題がもあるものも多いらしいです。


Gsonの目標は,

  • JavaオブジェクトからJSONへの変換とその逆の変換をするシンプルなtoJson()・fromJson()メソッドの提供
  • 元々ある修正不可能なオブジェクトのJSONへのそしてJSONからの変換を可能にする
  • Javaジェネリクのさらなるサポート
  • オブジェクトの特別な表現を可能にする
  • 任意の複雑なオブジェクトのサポート(深い継承階層とジェネリックの多用など)

らしい。




つかってみました。


こんな感じのシンプルなモデルを
Person.java

public class Person {
    private final String mName;
    private final int mAge;

    public Person(String name, int age) {
        super();
        mName = name;
        mAge = age;
    }

    public String getName() {
        return mName;
    }

    public int getAge() {
        return mAge;
    }
}


JSON形式にしてみました。



import com.google.gson.Gson;

public class Main {

    public static void main(String[] args) {
        Person taro = new Person("太郎", 20);

        Gson gson = new Gson();

        String jsonString = gson.toJson(taro);

        System.out.println(jsonString);
    }
}


実行結果はこのような感じになりました。



{"mName":"太郎","mAge":20}


複雑なモデルの変換とかはまたいつか。



それでは,また会えることを祈りつつ。

Javaの列挙型について(3)

前回,前々回に引き続き列挙型。


列挙型に組み込まれているいくつかのメソッドを確認。


Month.java

package com.rmstar.enumsample;

public enum Month {
    JANUARY("睦月", "冬"),
    FEBRUARY("如月", "冬"),
    MARCH("弥生", "春"),
    APRIL("卯月", "春"),
    MAY("皐月", "春"),
    JUNE("水無月", "夏"),
    JULY("文月", "夏"),
    AUGUST("葉月", "夏"),
    SEPTEMBER("長月", "秋"),
    OCTOBER("神無月", "秋"),
    NOVEMBER("霜月", "秋"),
    DECEMBER("師走", "冬"), ;

    private final String mOldJapaneseName;
    private final String mSeason;

    private Month(String oldJapaneseName, String season) {
        mOldJapaneseName = oldJapaneseName;
        mSeason = season;
    }

    public String getOldJapaneseName() {
        return mOldJapaneseName;
    }

    public String getSeason() {
        return mSeason;
    }
}

前回に引き続き月を表す列挙型を使います。





すべてのインスタンスを列挙する

    private static void showAll() {
        for (Month month : Month.values()) {
            System.out.println(month.getOldJapaneseName());
        }
    }

いちいちべた書きで,全要素を書く必要はありません。
staticメソッドMonth.values()で,全要素が入った配列を取得出来ます。



文字列と列挙型

列挙型で定義したインスタンスが使えるメソッド。
String name();

Object型で定義されているメソッド
String toString();

System.out.println(Month.JULY.toString());
System.out.println(Month.JULY.name());

実行結果は

JULY
JULY

となっています。

同じですね。
ただし,String name()は,finalがついていてオーバーライド禁止になっています。
String name()の返り値は常に列挙型を定義した際に
列挙した文字列(Monthだったら,JANUARYやDECEMBERなど)になります。

一方で,toString()はオーバーライド可能です。
より読みやすい文字列を表示出来るように自分で再定義することも可能です。
また,ライブラリ中の列挙型の場合,
name()より更に読みやすい名前が帰ってくることもあると思いますので,
デバックで用いるのならば,name()よりtoString()の方を選択する方がいいと思います。

static メソッド valueOf(String)
列挙型を定義する際に列挙した文字列を引数とした場合,
その列挙型のインスタンスを返します。
ちなみに列挙されていない文字列な場合,IllegalArgumentExceptionが投げられます。



compareTo()とordinal()

列挙型は,
Enumは Comparableインターフェースを実装しているので,
compareToメソッドが使えます。

System.out.println(Month.APRIL.compareTo(Month.JULY));
System.out.println(Month.JULY.compareTo(Month.JULY));
System.out.println(Month.JULY.compareTo(Month.APRIL));

実行結果は,以下の通り。

-3
0
3

列挙型を定義する際に,先に定義した要素が先に小さい・先に来るようになっています。


そのため次のように,TreeSetを使えばソートもしてくれます。

Set<Month> monthSet = new TreeSet<Month>();
monthSet.add(Month.DECEMBER);
monthSet.add(Month.JANUARY);
monthSet.add(Month.JULY);
System.out.println(monthSet);

実行結果は,

[JANUARY, JULY, DECEMBER]

となります。


int ordinal() メソッド

System.out.println(Month.JANUARY.ordinal());
System.out.println(Month.JULY.ordinal());
System.out.println(Month.DECEMBER.ordinal());

実行結果は,

0
6
11

となります。

その要素の序数を取得出来ます。
これとEnum.values()を用いて次の要素を取得するメソッドを書きました。

private static Month getNext(Month month) {
    Month[] values = Month.values();
    int index = (month.ordinal() + 1) % values.length;
    return values[index];
}

public static void main(String[] args) {
    System.out.println(getNext(Month.JANUARY));
    System.out.println(getNext(Month.JULY));
    System.out.println(getNext(Month.DECEMBER));
}


実行結果は,

FEBRUARY
AUGUST
JANUARY

となります。


それでは,また会えることを祈りつつ。

Javaの列挙型について(2)

前回に引き続き列挙型。



今回は月を表す列挙型と,その使用例。




Month.java

package com.rmstar.enumsample;

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER, ;
}


Main.java

package com.rmstar.enumsample;

public class Main {

    private static void showInOldJapanese(Month month) {
        switch (month) {
            case JANUARY:
                System.out.println("睦月");
                break;
            case FEBRUARY:
                System.out.println("如月");
                break;
            case MARCH:
                System.out.println("弥生");
                break;
            case APRIL:
                System.out.println("卯月");
                break;
            case MAY:
                System.out.println("皐月");
                break;
            case JUNE:
                System.out.println("水無月");
                break;
            case JULY:
                System.out.println("文月");
                break;
            case AUGUST:
                System.out.println("葉月");
                break;
            case SEPTEMBER:
                System.out.println("長月");
                break;
            case OCTOBER:
                System.out.println("神無月");
                break;
            case NOVEMBER:
                System.out.println("霜月");
                break;
            case DECEMBER:
                System.out.println("師走");
                break;
        }
    }

    private static void showSeason(Month month) {
        switch (month) {
            case MARCH:
            case APRIL:
            case MAY:
                System.out.println("春");
                break;
            case JUNE:
            case JULY:
            case AUGUST:
                System.out.println("夏");
                break;
            case SEPTEMBER:
            case OCTOBER:
            case NOVEMBER:
                System.out.println("秋");
                break;
            case DECEMBER:
            case JANUARY:
            case FEBRUARY:
                System.out.println("冬");
                break;
        }
    }

    public static void main(String[] args) {
        showInOldJapanese(Month.JULY);
        showSeason(Month.JULY);
    }
}



長い!
switch文...
これにもしかしたら,
1月と表示するメソッドとかDECとか表示するメソッドが加わるかもしれない。
良くない。





Javaの列挙型は,CやC#と違いクラス。



文月や夏といったものはJULYに紐づくものだから,
JULYインスタンスのフィールドに持たせて,ゲッタでアクセスするよう変更した。
変更後は以下の通り。




Month.java(変更後)

package com.rmstar.enumsample;

public enum Month {
    JANUARY("睦月", "冬"),
    FEBRUARY("如月", "冬"),
    MARCH("弥生", "春"),
    APRIL("卯月", "春"),
    MAY("皐月", "春"),
    JUNE("水無月", "夏"),
    JULY("文月", "夏"),
    AUGUST("葉月", "夏"),
    SEPTEMBER("長月", "秋"),
    OCTOBER("神無月", "秋"),
    NOVEMBER("霜月", "秋"),
    DECEMBER("師走", "冬"), ;

    private final String mOldJapaneseName;
    private final String mSeason;

    private Month(String oldJapaneseName, String season) {
        mOldJapaneseName = oldJapaneseName;
        mSeason = season;
    }

    public String getOldJapaneseName() {
        return mOldJapaneseName;
    }

    public String getSeason() {
        return mSeason;
    }
}


Main.java(変更後)

package com.rmstar.enumsample;

public class Main {
    public static void main(String[] args) {
        System.out.println(Month.JULY.getOldJapaneseName());
        System.out.println(Month.JULY.getSeason());
    }
}

すっきり。
もう一つ。


MonthのInstance全ての旧暦を表示したい。

        System.out.println(Month.JANUARY.getOldJapaneseName());
        System.out.println(Month.FEBRUARY.getOldJapaneseName());
        ・・・
        System.out.println(Month.DECEMBER.getOldJapaneseName());

という風に,書く?
めんどくさい。




こういうことを楽にする仕組みが列挙型にはあるらしい。
次回は,そのような列挙型でできることを確認したいと思います。






では,また会えることを祈りつつ。