事象
dynamodbエクスポートを実施し、S3へエクスポートされたDynamoDB JSONのファイルをダウンロードして解凍したら
このような形になってしまって、通常のJSONと形が違って、pythonでjsonとして扱いづらい。。。
{"Item":{"id":{"S":"aaaa"},"priority":{"BOOL":false},"address1":{"S":"東京都豊島区"}}
{"Item":{"id":{"S":"bbbbb"},"priority":{"BOOL":false},"longitude":{"N":"140.607"},"latitude":{"N":"34"},"address1":{"S":"神奈川県横浜市青葉区"}}
このように整形したい!
[
{"id": "aaaa", "priority": "", "address1": "東京都豊島区", "latitude": "", "longitude": ""},
{"id": "bbbbb", "priority": "", "longitude": "140.607", "latitude": "34", "address1": "神奈川県横浜市青葉区"}
]
DynamoDB JSONのままで何が悪い?
DynamoDB JSONの中身は、ただのstr型として扱われる
各行の最後尾にカンマ区切りがなく、dictか配列ではなく、ただのstrとなる
json.loadするときはエラーになる
json.decoder.JSONDecodeError: Extra data:
各項目の先頭には余計に「Item」キーがついてる
Jsonのエラーは出ないが、余計な情報なので消したい
各バリューには余計に「S」とか「N」とかのキーが入った
Jsonのエラーは出ないが、余計な情報なので消したい
数値が全部Decimal型となる
int型やfloat型の値が全部Decimal型となり、jsonとして読み込むときは、TypeErrorが発生
int型かfloat型かstr型、必要に応じて型変換したい
TypeError: Object of type Decimal is not JSON serializable
数値のデータ値が空の場合、そのキーバリューが丸ごと消えてしまう
「アドレス」キーの場合、文字列で空の状態で、 {address2":{"S":""}} としてエクスポートできるが、
「経度」キーや「緯度」キーなど、数値の場合、latitudeとlongitudeのキーバリューが消えてしまった。。。{latitude:{"N":""}}で出力できない。。。
ブーリアン値はそのままjsonとして扱えない
DynamoDB JSONは {"BOOL":true} の形で出力するので、 {"BOOL":"true"} のようにstr化しないと、jsonとして扱うときJSONDecodeErrorが発生
(今回は業務の都合で、 {"BOOL":"1"} にしたい)
json.decoder.JSONDecodeError: Extra data:
ファイルを読み込むときは、ちゃんとエンコードを指定しないといけない
encoding="utf-8" を指定しないと、UnicodeDecodeErrorが発生
UnicodeDecodeError: 'cp932' codec can't decode byte 0xef
やり方
いろんなサイトを見てソースを作った!
boto3ラブライブのTypeDeserializerで、「S」とか「N」とかを消す処理ができるが、ほかの整形は自分でやらないといけないらしい。。。
(もっといい方法ご存じの方は教えてください~)
import json
from boto3.dynamodb.types import TypeDeserializer
from decimal import Decimal
#str型へ変換。(dynamoDB JSONの数値をDecimal型なので、json.dumpできない)
def from_decimal_to_str(obj):
if isinstance(obj, Decimal):
return str(obj)
deserializer = TypeDeserializer()
#エクスポートしたdynamoDB JSONを読み込む。
with open("input.json", "r", encoding="utf-8") as input_json:
lines = input_json.readlines()
# ブーリアン値のtrue/falseを文字列化する、"BOOL":false → "BOOL":"","BOOL":true → "BOOL":"1"
lines = [line.replace("\"BOOL\":false","\"BOOL\":\"\"") for line in lines]
lines = [line.replace("\"BOOL\":true","\"BOOL\":\"1\"") for line in lines]
#Itemキーを削除
lines = [json.loads(line)["Item"] for line in lines]
#dynamoDB JSONから普通のJSONへ変換、"name":{"S":"aaaa"} → "name":"aaaa"
lines = [{k: deserializer.deserialize(v) for k, v in line.items()} for line in lines ]
#latitudeとlongitudeの欠損値を補完(数値が空の場合、dynamoDB JSONはその項目を勝手に削除するため)
for line in lines:
line.setdefault("latitude","")
line.setdefault("longitude","")
#変換後のファイルを出力
with open("output.json", "w", encoding="utf-8") as output_json:
output_json.writelines(json.dumps(lines, ensure_ascii=False, default=from_decimal_to_str))
参考サイト