S3へエクスポートされたDynamoDB JSONを通常のjsonに整形した

# 事象
dynamodbにこのようなデータがある
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/5eec331d-d1f5-97a7-d3b5-c383bbe2008b.png)



dynamodbエクスポートを実施し、S3へエクスポートされたDynamoDB JSONのファイルをダウンロードして解凍したら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/0cc577c9-d46f-20b5-5f97-fb2f9df3013e.png)

このような形になってしまって、通常のJSONと形が違って、pythonでjsonとして扱いづらい。。。
```ruby:
{"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":"神奈川県横浜市青葉区"}}
```

このように整形したい！

```ruby:qiita.rb
[
{"id": "aaaa", "priority": "", "address1": "東京都豊島区", "latitude": "", "longitude": ""},
{"id": "bbbbb", "priority": "", "longitude": "140.607", "latitude": "34", "address1": "神奈川県横浜市青葉区"}
]
```


# DynamoDB JSONのままで何が悪い？
### DynamoDB JSONの中身は、ただのstr型として扱われる
各行の最後尾にカンマ区切りがなく、dictか配列ではなく、ただのstrとなる
json.loadするときはエラーになる

```ruby:
json.decoder.JSONDecodeError: Extra data:
```

### 各項目の先頭には余計に「Item」キーがついてる
Jsonのエラーは出ないが、余計な情報なので消したい

### 各バリューには余計に「S」とか「N」とかのキーが入った
Jsonのエラーは出ないが、余計な情報なので消したい

### 数値が全部Decimal型となる
int型やfloat型の値が全部Decimal型となり、jsonとして読み込むときは、TypeErrorが発生
int型かfloat型かstr型、必要に応じて型変換したい

```ruby:
TypeError: Object of type Decimal is not JSON serializable
```


### 数値のデータ値が空の場合、そのキーバリューが丸ごと消えてしまう

「アドレス」キーの場合、文字列で空の状態で、 {address2":{"S":""}} としてエクスポートできるが、
「経度」キーや「緯度」キーなど、数値の場合、latitudeとlongitudeのキーバリューが消えてしまった。。。{latitude:{"N":""}}で出力できない。。。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538617/66373e68-8625-ded7-f42a-e984942a8333.png)


### ブーリアン値はそのままjsonとして扱えない

DynamoDB JSONは {"BOOL":true} の形で出力するので、 {"BOOL":"true"} のようにstr化しないと、jsonとして扱うときJSONDecodeErrorが発生
（今回は業務の都合で、 {"BOOL":"1"} にしたい）

```ruby:
json.decoder.JSONDecodeError: Extra data:
```

### ファイルを読み込むときは、ちゃんとエンコードを指定しないといけない
encoding="utf-8" を指定しないと、UnicodeDecodeErrorが発生
```ruby:
UnicodeDecodeError: 'cp932' codec can't decode byte 0xef
```

# やり方
いろんなサイトを見てソースを作った！
boto3ラブライブのTypeDeserializerで、「S」とか「N」とかを消す処理ができるが、ほかの整形は自分でやらないといけないらしい。。。
（もっといい方法ご存じの方は教えてください～）

```ruby:
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))

```



# 参考サイト
https://qiita.com/SF-28/items/d22fc20dae35c2aec27d

https://qiita.com/ekzemplaro/items/5fa8900212252ab554a3

https://stackoverflow.com/questions/11093021/python-decimal-to-string

https://note.nkmk.me/python-str-replace-translate-re-sub/

https://office54.net/python/error/unicode-decode-cp932#section1-1