はじめに
GSON のデフォルト動作では、バイト配列をシリアライズすると、数字の配列となります。
// 次のデータ構造を表す Map インスタンスを作成する
//
// {
// "binary": [ 18, 52, 86 ]
// }
//
Map<String, Object> input = new LinkedHashMap<>();
input.put("binary", { (byte)0x12, (byte)0x34, (byte)0x56 };
// Gson を用意する
Gson gson = new GsonBuilder().setPrettyPrinting().create();
// データを JSON に変換する
String json = gson.toJson(input);
// JSON を出力する
System.out.println(json);
{
"binary": [
18,
52,
86
]
}
しかし、場合によっては、バイト配列を base64url 文字列としてシリアライズしたいことがあるかもしれません。
{
"binary": "EjRW"
}
この記事では、バイト配列と base64url 文字列の双方向変換を GSON に行わせる方法を紹介します。
TypeAdapter
特定の型の変換処理をカスタマイズしたい場合、TypeAdapter のサブクラスを作って GsonBuilder に登録します。
TypeAdapter クラスには、未実装の抽象メソッドが二つあります。一つはシリアラズ処理時に呼ばれる write メソッド、もう一つはデシリアライズ時に呼ばれる read メソッドです。
今回は、write メソッドの実装では「受け取ったバイト配列を base64url 文字列に変換して書き出す」処理を、read メソッドの実装では「base64url 文字列を読み込んでバイト配列に変換して返す」処理を行います。
この処理の実装は、次のようになります。
Base64UrlAdapter.java
package com.example;
import java.io.IOException;
import java.util.Base64;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class Base64UrlAdapter extends TypeAdapter<byte[]>
{
@Override
public void write(JsonWriter out, byte[] value) throws IOException
{
if (value == null)
{
// null を書き出す
out.nullValue();
}
else
{
// バイト配列を base64url 文字列に変換して書き出す
out.value(toBase64Url(value));
}
}
@Override
public byte[] read(JsonReader in) throws IOException
{
if (in.peek() == JsonToken.NULL)
{
// null を読み込み、null を返す
in.nextNull();
return null;
}
// base64url 文字列を読み込み、バイト配列に変換して返す
return fromBase64Url(in.nextString());
}
private static String toBase64Url(byte[] value)
{
// バイト配列を base64url 文字列に変換する
return Base64.getUrlEncoder().withoutPadding().encodeToString(value);
}
private static byte[] fromBase64Url(String value)
{
// base64url 文字列をバイト配列に変換する
return Base64.getUrlDecoder().decode(value);
}
}
このように実装した Base64UrlAdapter を GsonBuilder の registerTypeAdapter メソッドで登録してから Gson インスタンスを作成すれば、準備完了です。
Gson gson = new GsonBuilder()
.registerTypeAdapter(byte[].class, new Base64UrlAdapter())
.create();
コード例
バイト配列から base64url 文字列へのシリアライズ処理と、base64url 文字列からバイト配列へのデシリアライズ処理、それぞれのコード例は次のとおりです。
@Test
public void test_to_base64url()
{
// Base64UrlAdapter を有効にして Gson を作成する
Gson gson = new GsonBuilder()
.registerTypeAdapter(byte[].class, new Base64UrlAdapter())
.create();
// 次のデータ構造を表す Map インスタンスを作成する
//
// {
// "binary": [ 18, 52, 86 ]
// }
//
Map<String, Object> input = new LinkedHashMap<>();
input.put("binary", new byte[]{ (byte)0x12, (byte)0x34, (byte)0x56 });
// JSON へと変換する
String json = gson.toJson(input);
// JSON の内容は {"binary":"EjRW"} になっているはず
assertEquals(json, "{\"binary\":\"EjRW\"}");
}
@Test
public void test_from_base64url()
{
// Base64UrlAdapter を有効にして Gson を作成する
Gson gson = new GsonBuilder()
.registerTypeAdapter(byte[].class, new Base64UrlAdapter())
.create();
// {"binary":"EjRW"} という内容を持つ JSON を用意する
String input = "{\"binary\":\"EjRW\"}";
// JSON をデシリアライズして Data クラスのインスタンスを得る
Data data = gson.fromJson(input, Data.class);
// バイト配列 data.binary の長さが 3 で、その要素の値が先頭から
// (byte)0x12, (byte)0x34, (byte)0x56 であることを確認する
assertNotNull(data.binary);
assertEquals(3, data.binary.length);
assertEquals((byte)0x12, data.binary[0]);
assertEquals((byte)0x34, data.binary[1]);
assertEquals((byte)0x56, data.binary[2]);
}
private static class Data
{
byte[] binary;
}
おわりに
Token Status List という仕様の StatusList 構造の lst プロパティの値は、CBOR フォーマットではバイト配列ですが、JSON フォーマットでは base64url 文字列となっています。このようなデータ構造を扱うときに、今回作成した Base64UrlAdapter を使うことがあるかもしれません。