Python で JSON データを扱う実践的な方法

REST API を利用する際、レスポンスは JSON 形式であることが多いのではないでしょうか ?
API から返された JSON 形式のデータを Python のオブジェクトに詰め直して処理したり、逆に Python 上での処理結果を JSON 形式に変換して API にするケースについて解説します。

本記事では、JSON の基本的な概念から、Python での具体的な処理方法、実践的なユースケース、さらには日本語対応やカスタムクラスの変換まで、幅広く解説していきます。

## 1. JSON の基本

JSON は JavaScript Object Notation の略で、データを表現するための形式です。

https://ecma-international.org/publications-and-standards/standards/ecma-404/

以下のような特徴があります：

- 軽量なテキスト形式
- プログラミング言語に依存しない
- データ型として、文字列、数値、真偽値、null、配列、オブジェクトをサポート

## Python で JSON を扱うための準備

Python には標準ライブラリとして`json`モジュールが用意されています。

```python
import json
```

これだけで JSON 処理に必要な機能が使えるようになります。
※ これ以降のサンプルコードは、こちらの `import json` を省略して記載します

## JSON 文字列から Python オブジェクトへの変換

### 基本的な使い方

`json.loads()` を使用することで変換できます。

```python
# JSON文字列
json_str = '''
{
    "name": "pokapu",
    "age": 37,
    "is_married": true,
    "hobbies": ["play guitar", "programming"]
}
'''

# JSON文字列をPythonオブジェクトに変換
data = json.loads(json_str)

# Python の Dict として処理
print(data["name"])  # pokapu
print(data["hobbies"][0])  # play guitar
```

上記で記載した `json_str` の場合、`json.loads()` の結果は `Dict` になります。
下記のように変換対象の JSON 文字列のトップレベル要素が配列であれば、`json.loads()` の結果は `List[dict]` になります。

```python
# JSON文字列
json_str = '''
[
    {
        "name": "apple",
        "color": "red"
    },
    {
        "name": "orange",
        "color": "orange"
    },
    {
        "name": "banana",
        "color": "yellow"
    }
]
'''

# JSON文字列をPythonオブジェクトに変換
data = json.loads(json_str)

# Python の Dict の List として処理
print(data[0]["name"])  # apple
print(data[0]["color"])  # red
```

### 実践的なユースケース：API レスポンスの処理

[他の記事](https://qiita.com/pokapu/items/2ba221568a59ef6f72de) で紹介した Requests ライブラリを使用すると、API レスポンスの JSON をスムーズに Python 上で処理できます。

```python
import requests

# API からデータを取得する例
response = requests.get("https://api.example.com/users/1")
user_data = response.json()  # requests ライブラリは自動的に JSON をパースします

# データの処理
name = user_data["name"]
email = user_data["email"]
```

## Python オブジェクトから JSON への変換

### 基本的な使い方

Python 上で定義した `Dict` (や `List[Dict]`) のデータは、`json.dumps()` を使用して簡単に JSON 文字列に変換できます。

```python
# Pythonのデータ
user = {
    "name": "pokapu",
    "age": 37,
    "preferences": {
        "color": "青",
        "food": ["チョコ", "かつ丼"]
    }
}

# JSON文字列に変換
json_str = json.dumps(user, ensure_ascii=False, indent=2)
print(json_str)
```

↓ 出力結果

```json
{
  "name": "pokapu",
  "age": 37,
  "preferences": {
    "color": "青",
    "food": ["チョコ", "かつ丼"]
  }
}
```

### 日本語の扱い

先ほどの例のように `ensure_ascii=False` を指定することで、日本語のような非 ASCII 文字を直接 JSON に出力できます。
指定しない場合、非 ASCII 文字は `\uXXXX` 形式でエスケープされます。

```python
# Pythonのデータ
user = {
    "name": "pokapu",
    "age": 37,
    "preferences": {
        "color": "青",
        "food": ["チョコ", "かつ丼"]
    }
}

# JSON文字列に変換
json_str = json.dumps(user, indent=2)
print(json_str)
```

↓ 出力結果

```json
{
  "name": "pokapu",
  "age": 37,
  "preferences": {
    "color": "\u9752",
    "food": ["\u30c1\u30e7\u30b3", "\u304b\u3064\u4e3c"]
  }
}
```

### カスタムクラスの変換

`json.JSONEncoder` を継承したエンコーダーを使用することで、Python 上のオブジェクトの変換ルールをカスタマイズできます。
例えば日付データを ISO フォーマット（ISO 8601）の文字列で json に含めたい場合は、下記のような記述で実現できます。

```python
from datetime import datetime

class User:
    def __init__(self, name, created_at):
        self.name = name
        self.created_at = created_at

    def to_dict(self):
        return {
            "name": self.name,
            "created_at": self.created_at.isoformat()
        }

# カスタムJSONエンコーダー
class UserJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, User):
            return obj.to_dict()
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

# 使用例
user = User("pokapu", datetime.now())
json_str = json.dumps(user, cls=UserJSONEncoder, indent=2)
print(json_str)
```

↓ 出力結果

```json
{
  "name": "pokapu",
  "created_at": "2024-11-21T05:59:21.919900"
}
```

## エラーハンドリング

`json.loads()` に間違った形式の文字列を渡すと、`json.JSONDecodeError` が発生します。

```python
try:
    # 不正なJSON文字列
    invalid_json = '{"name": "pokapu", age: 37}'
    data = json.loads(invalid_json)
except json.JSONDecodeError as e:
    print(f"JSONのパースに失敗しました: {e}")
```

↓ 出力結果

```
JSONのパースに失敗しました: Expecting property name enclosed in double quotes: line 1 column 20 (char 19)
```

出力結果を見ると分かるように、文字列のどの位置に問題があるかを例外オブジェクトに含めてくれるので、こちらを参考に修正が実施できますね。

## まとめ

Python では`json`モジュールを使用することで、簡単に JSON データを扱うことができます。
API との連携や設定ファイルの読み書きなど、様々な場面で活用できる重要な機能です。
基本的な使い方を押さえた上で、必要に応じて日本語対応やカスタムエンコーディングなどの応用的な処理を組み合わせることで、より柔軟な JSON 処理が実現できます。