DynamoDB JSONの型変換をboto3だけで行ってみる
こんばんは、CX事業本部Delivery部の夏目です。
本日boto3がResource Interfaceを廃止する方向というニュースが飛び込んで来まして、早速DynamoDBのPutとGetをClient Interfaceで行うブログが投稿されました。
これはDynamoDBにおいてClient Interfaceを使用使用するとDynamoDB JSONという特殊な形式でデータが返ってくるため使い勝手が悪く、一般的にResource Interfaceが使用されていたので急ぎ記事にした、というもの(と私は解釈している)。
boto3のclientでDynamoDBテーブルを利用してみる(scan, put_item, get_item, update_item, delete_item)
この記事において、DynamoDB JSON形式では型情報を持たせる必要があるため、dictからの型変換が大変だという記述があります。
DynamoDBテーブルの型情報が必要なのがとても手間です。 その代替策として、とりあえず、2つありそうです。
https://dev.classmethod.jp/articles/boto3-client-dynamodb-sample/#toc-10
記事では外部のライブラリを使用する方法を紹介しているのですが、実はboto3の中に型変換のためのクラスが存在しているので紹介します。
from boto3.dynamodb.types import TypeSerializer, TypeDeserializer
使うのはTypeSerializer
とTypeDeserializer
です。
わかりやすく言うと、TypeSerializer
がdictからDynamoDB JSONへの変換を、TypeDeserializer
がDynamoDB JSONからdictへの変換を、行うためのクラスです。
正確には、Pythonの値をDynamoDB JSONの値にSerializeしたり、DynamoDB JSONの値をPythonの値にDeserializeしたりする、クラスです。
言葉ではわかりにくいので実際に動かしてみます。
TypeSerializerの使い方
from boto3.dynamodb.types import TypeSerializer serializer = TypeSerializer() item_python_dict = { "todoId": "t0001", "title": "this is title", "status": "unstarted", "createdAt": 1674124101857, "binary": b"pen", "any string set": {"Tag Name1", "Tag Name2", "TagName3"}, "any number set": {111, 222, 333}, "any binary set": [b"aaa", b"bbb", b"ccc"], "any map": {"map-name1": "map-value1", "map-name2": 111}, "any list": ["list-value1", "222"], "any null": None, "any bol": False, } item_dynamodb_json = { k: serializer.serialize(v) for k, v in item_python_dict.items() } print(json.dumps(item_dynamodb_json, indent=4))
{'todoId': {'S': 't0001'}, 'title': {'S': 'this is title'}, 'status': {'S': 'unstarted'}, 'createdAt': {'N': '1674124101857'}, 'binary': {'B': b'pen'}, 'any string set': {'SS': ['Tag Name1', 'Tag Name2', 'TagName3']}, 'any number set': {'NS': ['333', '222', '111']}, 'any binary set': {'L': [{'B': b'aaa'}, {'B': b'bbb'}, {'B': b'ccc'}]}, 'any map': {'M': {'map-name1': {'S': 'map-value1'}, 'map-name2': {'N': '111'}}}, 'any list': {'L': [{'S': 'list-value1'}, {'S': '222'}]}, 'any null': {'NULL': True}, 'any bol': {'BOOL': False}}
{ 'todoId': {'S': 't0001'}, 'title': {'S': 'this is title'}, 'status': {'S': 'unstarted'}, 'createdAt': {'N': '1674124101857'}, 'binary': {'B': b'pen'}, 'any string set': { 'SS': ['Tag Name1', 'Tag Name2', 'TagName3'] }, 'any number set': { 'NS': ['333', '222', '111'] }, 'any binary set': { 'L': [{'B': b'aaa'}, {'B': b'bbb'}, {'B': b'ccc'}] }, 'any map': { 'M': { 'map-name1': {'S': 'map-value1'}, 'map-name2': {'N': '111'} } }, 'any list': { 'L': [{'S': 'list-value1'}, {'S': '222'}] }, 'any null': {'NULL': True}, 'any bol': {'BOOL': False} }
コードの20から23行目を見るとわかるのですが、TypeSerializer().serialize()
にはdict全体を渡さず、Key-ValueのValueだけ渡していることがわかります。
TypeSerializer
はPythonで扱う型の値をDynamoDB JSONの型の値に変換するものです。
TypeDeserializer
TypeDeserializer
はTypeSerializer
と逆のことを行うので、DynamoDB JSONの値をPythonの型の値に変換するクラスです。
from boto3.dynamodb.types import TypeDeserializer deserializer = TypeDeserializer() item_dynamodb_json = { 'todoId': {'S': 't0001'}, 'title': {'S': 'this is title'}, 'status': {'S': 'unstarted'}, 'createdAt': {'N': '1674124101857'}, 'binary': {'B': b'pen'}, 'any string set': { 'SS': ['Tag Name1', 'Tag Name2', 'TagName3'] }, 'any number set': { 'NS': ['333', '222', '111'] }, 'any binary set': { 'L': [{'B': b'aaa'}, {'B': b'bbb'}, {'B': b'ccc'}] }, 'any map': { 'M': { 'map-name1': {'S': 'map-value1'}, 'map-name2': {'N': '111'} } }, 'any list': { 'L': [{'S': 'list-value1'}, {'S': '222'}] }, 'any null': {'NULL': True}, 'any bol': {'BOOL': False} } item_python_dict = { k: deserializer.deserialize(v) for k, v in item_dynamodb_json.items() } print(item_python_dict)
{'todoId': 't0001', 'title': 'this is title', 'status': 'unstarted', 'createdAt': Decimal('1674124101857'), 'binary': Binary(b'pen'), 'any string set': {'TagName3', 'Tag Name1', 'Tag Name2'}, 'any number set': {Decimal('333'), Decimal('222'), Decimal('111')}, 'any binary set': [Binary(b'aaa'), Binary(b'bbb'), Binary(b'ccc')], 'any map': {'map-name1': 'map-value1', 'map-name2': Decimal('111')}, 'any list': ['list-value1', '222'], 'any null': None, 'any bol': False}
{ 'todoId': 't0001', 'title': 'this is title', 'status': 'unstarted', 'createdAt': Decimal('1674124101857'), 'binary': Binary(b'pen'), 'any string set': {'TagName3', 'Tag Name2', 'Tag Name1'}, 'any number set': {Decimal('333'), Decimal('222'), Decimal('111')}, 'any binary set': [Binary(b'aaa'), Binary(b'bbb'), Binary(b'ccc')], 'any map': { 'map-name1': 'map-value1', 'map-name2': Decimal('111') }, 'any list': ['list-value1', '222'], 'any null': None, 'any bol': False }
こちらもTypeDeserializer().deserializer()
に渡しているのは、DynamoDB JSON全体ではなく、Key-ValueのValueだけです。
Deserializeの際には完全にPythonの型に変換されるのではなく、バイナリの値だけはboto3.dynamodb.types.Binary
に変換されます。
(boto3で定義されているラッパー型。Binary(b'pen').value
で実際の値にアクセスできる)
まとめ
以上、DynamoDB JSONを変換するためのコードはboto3に既に実装されている、ということです。
実は、Resource Clientを使用できる現状でも使い道がありまして、DynamoDB StreamをトリガーとするLambdaのeventにはDynamoDB JSON形式でデータが格納されているのでTypeDeserializer
にはお世話になることがあります。
Resource Interfaceが完全に廃止された際にTypeSerializerとTypeDeserializerも削除されるということがなければ、boto3だけで型変換もできるはずです。
P.S.
DynamoDBを使用する上で、boto3ではReource Interfaceにおいてbatch_writer()
もよく使います。
こちらは自分の知る限りResource Interfaceでしか使えないので廃止の際にClientに移植されないかなぁと思ったりしています。
参考
https://boto3.amazonaws.com/v1/documentation/api/latest/_modules/boto3/dynamodb/types.html