GSONで単一か配列かわからない項目を扱う方法

# いきさつ

Redmine REST APIでJSONを取得した際、"custom_fields"に以下のような表記が現れました。

```json
"custom_fields": [
  {
    "id": 1,
    "name": "項目１",
    "value": "a"
  },
  {
    "id": 2,
    "name": "項目２",
    "multiple": true,
    "value": [
      "b"
    ]
  },
  {
    "id": 3,
    "name": "項目３",
    "multiple": true,
    "value": [
      "c",
      "d"
    ]
  },
```

`"value"`の内容が`"multiple" : true`の場合は配列オブジェクトで、それ以外の場合は単一となるようです。
上記の例の通り３パターンあります。

1. 単一項目（multiple指定なし）
2. 配列で要素数が1つ
3. 配列で要素数が2つ以上

デシリアライズ（JSON→Java）時に単一の場合も全て配列に変換する方法もありますが、シリアライズ時に元の表現に戻せない（単一なのか要素数1の配列かわからない）ので、その制御を入れることを前提として、ついでに汎用的に使える実装を考えてみました。

# 実装

## データオブジェクト

デシリアライズ時にデータを入れるオブジェクトは以下のようになっています。

```java
public class MultipleType<T> {

	private T value;

	private List<T> values;

	public boolean isMultiple() {
		return values != null;
	}

	//以下getter/setter

}
```

valueまたはvaluesのどちらかを使用し、もう片方はnullとするようにします。

上記の"custom_fields"だと以下のような感じです。

```java
public class CustomField {
	private String id;
	private String name;
	private boolean multiple;
	private MultipleType<String> value;

	//以下getter/setter
}
```

GSONの機能を極力使用するようにしたいので、JSONとフィールド名は同じにします。

## シリアライザ／デシリアライザ

GSONのカスタムシリアライザ／デシリアライザを以下のように実装します。

```java
public class MultipleTypeAdapter implements JsonDeserializer<MultipleType<?>>, JsonSerializer<MultipleType<?>> {

	@Override
	public MultipleType<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
			throws JsonParseException {

		final MultipleType<?> result = new MultipleType<>();

		if (json.isJsonArray()) {
			result.setValues(deserializeArray(json, typeOfT, context));
		} else {
			result.setValue(context.deserialize(json, getGenericType(typeOfT)));
		}
		return result;
	}

	private <T> List<T> deserializeArray(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
		final List<T> values = new ArrayList<>();
		final Type t = getGenericType(typeOfT);
		for (JsonElement e : json.getAsJsonArray()) {
			values.add(context.deserialize(e, t));
		}
		return values;
	}

	/* get actual Type of <?> */
	private Type getGenericType(Type typeOfT) {
		return ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
	}

	@Override
	public JsonElement serialize(MultipleType<?> src, Type typeOfSrc, JsonSerializationContext context) {
		return context.serialize(src.isMultiple() ? src.getValues() : src.getValue());
	}

}
```

要点は３つです。

1. デシリアライズ時は`isJsonArray()`で配列かどうかチェックして処理を切り替える
2. `MultipleType`の型パラメータを取得する
3. シリアライズ時は`isMultiple()`で配列かどうかチェックし処理を切り替える

特に２がちょっと複雑です。
まず、`deserialize `メソッドの引数`Type typeOfT`が何を表しているかを知る必要があります。
これは、`JsonDeserializer`インターフェイスの型パラメータを表すオブジェクトが入っています。
（正確には実行時に実際に使われているオブジェクトの型）
ジェネリクス型の型パラメータはコンパイル時に失われてしまうので、実行時に参照できるようになっているのですね。

定義からわかるのは`typeOfT`は`MultipleType<?>`であるということですが、`<?>`の部分を知る必要があるため、`getGenericType`メソッドでさらに内部の型パラメータを取得しています。

なぜ型パラメータ`<?>`の実行時型が必要かというと、`context.deserialize`メソッドで正しい型を与えてあげると、それ以降のデシリアライズはGSONの機能に任せることができるためです。
特にデータオブジェクトのフィールドがプリミティブ型（String含む）ではない型であるような複雑な構造のデシリアライズを手書きせずにすみます。

## GSON使用箇所

GSONの使用箇所は以下のようにします。

```java
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MultipleType.class, new MultipleTypeAdapter());
final Gson gson = gsonBuilder.create();
```

`MultipleTypeAdapter`クラスがジェネリクス型じゃないので警告がでないのがミソだったりします。
カスタムデシリアライザ／シリアライザの型パラメータに`<?>`(ワイルドカード)を使った理由がこれです。
（なぜワイルドカードならOKなのかの説明はできません・・・）

# ところで

私はJavaの総称型は文章では「ジェネリクス」で、発音は「ジェネリック」としています。
「ジェネリクス」って言いにくくないですか？