PythonでJSON 書き込み

## 早めの結論
- json に変換するには `json.dumps` 関数を使用する
- json ファイルに書き込むには `json.dump` 関数を使用する
- 変換できない型には `defalut` を指定すると良い
- 変換前後の型対応については[こちら](#型の対応)

## json に変換する

```Python
import json

# json に書き込むデータ
target_dict = {
    "int": 42,
    "float": 0.5,
    "bool_true": True,
    "bool_false": False,
    "none": None,
    "str": "spam",
    "list": ["eggs", "ham"],
    "tapple": ("this", "is", "tapple", 42),
    "dict": {"key1": 1, "key2": 2}
}

# json にする
target_dumped_str = json.dumps(target_dict)
```

### 出力結果
json に変換したデータ `target_dumped_str` の中身は以下です

```Python
(
    '{
        "int": 42,
        "float": 0.5,
        "bool_true": true,
        "bool_false": false,
        "none": null,
        "str": "spam",
        "list": ["eggs", "ham"],
        "tapple": ["this", "is", "tapple", 42],
        "dict": {"key1": 1, "key2": 2}
    }'
)
```

## json ファイルに書き込む
おつぎは，json ファイルに書き込みたいと思います

```Python
import json

# json に書き込むデータ
target_dict = {
    "int": 42,
    "float": 0.5,
    "bool_true": True,
    "bool_false": False,
    "none": None,
    "str": "spam",
    "list": ["eggs", "ham"],
    "tapple": ("this", "is", "tapple", 42),
    "dict": {"key1": 1, "key2": 2}
}

# ファイルに書き込む
with open("qiita_write.json", mode="w", encoding="utf-8") as f:
    json.dump(target_dict, f)

```

### 出力結果

```Json:qiita_write.json
{
    "int": 42,
    "float": 0.5,
    "bool_true": true,
    "bool_false": false,
    "none": null,
    "str": "spam",
    "list": ["eggs", "ham"],
    "tapple": ["this", "is", "tapple", 42],
    "dict": {"key1": 1, "key2": 2}
}
```

## 型の対応
ここまで書き込みを見てきましたが，

- 変換した後の型はどうなってるの？
- あの型は変換できるの？

など型について疑問に思った方もいるかと思います．

ということでまずは型の対応を見てみましょう．

### デコードの対応（JSON → Python）

|JSON|Python|
|--|--|
|object|dict|
|array|list|
|string|str|
|number (int)|int|
|number (real)|float|
|true|True|
|false|False|
|null|None|

[公式ドキュメント](https://docs.python.org/3/library/json.html#encoders-and-decoders)

### エンコードの対応（Python → JSON）
|Python|JSON|
|--|--|
|dict|object|
|list, tupple|array|
|str|string|
|int, float, int- & float-derived Enums|number|
|True|true|
|False|false|
|None|null|

[公式ドキュメント](https://docs.python.org/3/library/json.html#json.JSONEncoder)

## 変換できない型の対処
上記で型の対応を見ましたが，`Decimal` や `datatime` などはどうなるのでしょうか...？
ということでやってみましょう．

### 変換チャレンジ
```Python
import json
from decimal import Decimal

# エラーになるデータ
error_dict = {
    "decimal": Decimal("42"),
}

# json にする
target_dumped_str = json.dumps(error_dict, default=json_type_handle)
```

### 出力結果
```Python
TypeError: Decimal('42') is not JSON serializable
```
エラーが出ましたね...

## 対処法
1. JSON で対応できるように変換する関数を作る
1. 書き込む関数の引数 `defalut` に 1 で作成した関数を指定する

### JSON で対応できるように変換する関数を作る

```Python
def json_type_handle(target):
    """"変換できない値を json で扱える値に変換する"""

    if isinstance(target, Decimal):
        return float(target)
    
    if isinstance(target, datetime):
        return target.isoformat()
    
    raise TypeError(f"{repr(target)} is not JSON serializable")
```

中身としては以下の通りです．
- `isinstance` 関数で型をチェックし，変換して返す
    - `Decimal` の場合は `float` に変換して返す
    - `datatime` の場合は `isoformat` 関数で文字列に変換して返す
- どれにも該当しなかったら例外を送出する


### 書き込む関数の引数 `defalut` に作成した関数を指定する

`default="作成した関数名"` で指定します．
以下具体例です．

```Python
# json にする
target_dumped_str = json.dumps(error_dict, default=json_type_handle)
pprint.pprint(target_dumped_str)

# ファイルに書き込む
with open("qiita_write_error_handle.json", mode="w", encoding="utf-8") as f:
    json.dump(error_dict, f, default=json_type_handle)
```

### 全体像
ということで全体像を見てみましょう．

```Python
import json
from decimal import Decimal
from datetime import datetime


# エラーになるデータ
error_dict = {
    "decimal": Decimal("42"),
    "datatime": datetime.now(),
}


def json_type_handle(target):
    """"変換できない値を json で扱える値に変換する"""

    if isinstance(target, Decimal):
        return float(target)
    
    if isinstance(target, datetime):
        return target.isoformat()
    
    raise TypeError(f"{repr(target)} is not JSON serializable")

# json にする
target_dumped_str = json.dumps(error_dict, default=json_type_handle)
pprint.pprint(target_dumped_str)

# ファイルに書き込む
with open("qiita_write_error_handle.json", mode="w", encoding="utf-8") as f:
    json.dump(error_dict, f, default=json_type_handle)
```

### 読み込むとき
上記の例では，

- Decimal → flaot 
- datatime → str

に変換しました．
逆に json を読み込んで Python で扱うときに，

- float → Decimal
- str → datatime

に変換したいとします．

```Python
# float を Decimal に変換して読み込む
target_loaded_str = json.loads(target_dumped_str, parse_float=Decimal,)

# str に変換されている datatime を datatime に変換する
datetime.fromisoformat(target_loaded_str.get("datatime"))
```

出力してみるとこんな感じです．

```Python
{'datatime': '2024-03-17T12:52:01.528393', 'decimal': Decimal('42.0')}
2024-03-17 12:52:01.528393
```

これで書き込みもばっちりですね!!
お疲れさまでした!!

## 参考
https://qiita.com/kikuchiTakuya/items/53990fca06fb9ba1d8a7