ユースケース
Amazon DynamoDB の変更データキャプチャを通したり、Amazon Kinesis Data FirehoseでS3保存したりするとDynamoDB 低レベル API のフォーマットで取り扱うことになります。
低レベルフォーマット例
{
"Age": {"N": "8"},
"Colors": {
"L": [
{"S": "White"},
{"S": "Brown"},
{"S": "Black"}
]
},
"Name": {"S": "Fido"},
"Vaccinations": {
"M": {
"Rabies": {
"L": [
{"S": "2009-03-17"},
{"S": "2011-09-21"},
{"S": "2014-07-08"}
]
},
"Distemper": {"S": "2015-10-13"}
}
},
"Breed": {"S": "Beagle"},
"AnimalType": {"S": "Dog"}
}
しかし、JavaではSDKよりDynamoDBMapperという高レベルAPIが提供されており、それに合わせてPOJOを用意してることが多いかと思います。
低レベルAPIのフォーマットは「JSONなので、、、」と安直にJackson等でこのPOJOをそのまま使おうとすると「データ型記述子(en:Data Type Descriptors)]」が邪魔になり扱えません。
そういった状況への対応の1つです。
方法
DynamoDBMapperで使える形式に変換してから渡す。
注釈
- 今回は単一レコードを処理対象とします。
- SomePOJO.classはDynamoDBMapperで使えるPOJOとして定義されてるものとします。
- dynamoDBMapperは任意のconfigを施したものを定義したものとします。
import static com.amazonaws.protocol.json.SdkStructuredPlainJsonFactory.JSON_CUSTOM_TYPE_UNMARSHALLERS;
import static com.amazonaws.protocol.json.SdkStructuredPlainJsonFactory.JSON_FACTORY;
import static com.amazonaws.protocol.json.SdkStructuredPlainJsonFactory.JSON_SCALAR_UNMARSHALLERS;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Map;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.transform.AttributeValueJsonUnmarshaller;
import com.amazonaws.transform.JsonUnmarshallerContext;
import com.amazonaws.transform.JsonUnmarshallerContextImpl;
import com.amazonaws.transform.MapUnmarshaller;
import com.fasterxml.jackson.core.JsonParser;
//①
InputStream src = new ByteArrayInputStream("生JSON".getBytes());
//②
JsonParser jsonParser = JSON_FACTORY.createParser(src);
//③
JsonUnmarshallerContext context = new JsonUnmarshallerContextImpl(
jsonParser,
JSON_SCALAR_UNMARSHALLERS, JSON_CUSTOM_TYPE_UNMARSHALLERS,
null
);
//④
MapUnmarshaller<String, AttributeValue> mapUnmarshaller = new MapUnmarshaller<>(
context.getUnmarshaller(String.class),
AttributeValueJsonUnmarshaller.getInstance()
);
//==========================================================
//⑤
Map<String, AttributeValue> itemAttributes = mapUnmarshaller.unmarshall(context);
//⑥
SomePOJO pojo = dynamoDBMapper.marshallIntoObject(SomePOJO.class, itemAttributes);
解説
大きく分けて2段階の⑤⑥とその準備の①~④に分かれる。
①ソースの準備
低レベルフォーマットを任意のInputStreamで用意する。
べた書きなら例にもあるようにByteArrayInputStream
ファイルから読むならFiles#newInputStreamなど
②パーサーの準備
パーサーへ①で作成したInputStreamを入力する。
この際、パーサーは使い捨てとなるので注意
③コンテキストの作成
②で用意したデータと定数を入力する。
この定数はcom.amazonaws.http.JsonResponseHandlerのsimpleTypeUnmarshallersとcustomTypeMarshallersの定義を逆引きしていくと記載の定数にたどり着く
第四引数は本来com.amazonaws.http.HttpResponseだが⑤の一次変換を行う上では使われない。
④Unmarshallerの準備
前段で使う変換定義となる。
今回はcom.amazonaws.services.dynamodbv2.model.transform.QueryResultJsonUnmarshallerの記述と合わせておく
なお、contextは前述のsimpleTypeUnmarshallersで固定されているため、contextを使用せずJSON_SCALAR_UNMARSHALLERSから直接記述しても同様の効果となると思われる。
⑤一次変換
dynamoDBMapperへ入力できるクラスへ変換する。
①-⑤まではすべてこのための準備となる。
⑥二次変換(POJO)
変換先POJOクラスと一次変換の結果を渡す。