Python標準ライブラリ「json」徹底解説:データ交換の要をマスターしよう!

JSON(JavaScript Object Notation)は、軽量なデータ交換フォーマットであり、現代のウェブ開発やAPI連携において不可欠な存在です。人間にとって読み書きが容易であり、同時にコンピュータにとっても解析や生成がしやすいという特徴を持っています。

もともとはJavaScriptのオブジェクトリテラル記法に由来しますが、特定のプログラミング言語に依存せず、Python、Java、Ruby、PHPなど、多くの言語で広くサポートされています。主に以下のような場面で活躍します。

  • Web API(サーバーとクライアント間のデータ通信)
  • 設定ファイル(アプリケーションの設定情報を記述)
  • 異なるシステム間のデータ交換
  • ドキュメント指向データベースでのデータ格納

Pythonには、このJSONデータを簡単に扱うための強力な標準ライブラリ json が組み込まれています。追加のインストールは不要で、インポートするだけで利用可能です。このライブラリを使えば、Pythonのオブジェクト(辞書やリストなど)をJSON形式の文字列やファイルに変換(シリアライズまたはエンコード)したり、逆にJSON形式の文字列やファイルをPythonオブジェクトに変換(デシリアライズまたはデコード)したりすることができます。

このブログ記事では、Pythonの json ライブラリの基本的な使い方から、少し応用的なテクニックまで、具体的なコード例を交えながら詳しく解説していきます。さあ、データ交換の要となるJSONの世界をPythonで探求しましょう!

import json

上記のコードで json モジュールをインポートすれば、準備完了です!

json モジュールには、主に4つの重要な関数があります。これらは、PythonオブジェクトとJSONデータ(文字列またはファイル)を相互に変換する役割を担います。

1. json.dumps():Pythonオブジェクト → JSON文字列

Pythonの辞書やリストなどのオブジェクトをJSON形式の文字列に変換(シリアライズ)します。「dumps」の「s」はstring(文字列)を意味すると覚えると良いでしょう。

import json

python_dict = {
    "name": "山田 太郎",
    "age": 30,
    "isStudent": False,
    "courses": ["Python", "データサイエンス"],
    "address": {
        "city": "東京都",
        "zip": "100-0001"
    },
    "score": None
}

# Python辞書をJSON文字列に変換
json_string = json.dumps(python_dict, ensure_ascii=False, indent=4, sort_keys=True)

print("--- JSON 文字列 ---")
print(json_string)
print("\n--- 型 ---")
print(type(json_string))

出力結果:

--- JSON 文字列 ---
{
    "address": {
        "city": "東京都",
        "zip": "100-0001"
    },
    "age": 30,
    "courses": [
        "Python",
        "データサイエンス"
    ],
    "isStudent": false,
    "name": "山田 太郎",
    "score": null
}

--- 型 ---
<class 'str'>

dumps() 関数にはいくつかの便利な引数があります。

  • ensure_ascii=False: デフォルトではASCII文字以外は \uXXXX のようにエスケープされますが、False に設定すると日本語などのマルチバイト文字をそのまま出力します。UTF-8でエンコードされた文字列が出力されます。
  • indent=数値: 出力されるJSON文字列を読みやすくするためにインデントを設定します。通常は 24 が使われます。整数だけでなく文字列(例: "\t")も指定可能です(Python 3.2以降)。
  • sort_keys=True: 辞書のキーをアルファベット順(辞書順)にソートして出力します。これにより、出力結果が常に一定になり、比較などが容易になります。
  • separators=(アイテム区切り文字, キーと値の区切り文字): デフォルトは (', ', ': ') です。indent=None の場合に (',', ':') を指定すると、空白が省略され、最もコンパクトな表現になります。

2. json.loads():JSON文字列 → Pythonオブジェクト

JSON形式の文字列をPythonのオブジェクト(通常は辞書またはリスト)に変換(デシリアライズ)します。「loads」の「s」はこちらもstring(文字列)を意味します。

import json

json_string = """
{
    "name": "鈴木 花子",
    "age": 25,
    "city": "大阪市",
    "hobbies": ["読書", "旅行"],
    "isActive": true,
    "score": null
}
"""

# JSON文字列をPythonオブジェクト (辞書) に変換
python_obj = json.loads(json_string)

print("--- Python オブジェクト ---")
print(python_obj)
print("\n--- 型 ---")
print(type(python_obj))
print("\n--- アクセス例 ---")
print(f"名前: {python_obj['name']}")
print(f"趣味の1つ目: {python_obj['hobbies'][0]}")

出力結果:

--- Python オブジェクト ---
{'name': '鈴木 花子', 'age': 25, 'city': '大阪市', 'hobbies': ['読書', '旅行'], 'isActive': True, 'score': None}

--- 型 ---
<class 'dict'>

--- アクセス例 ---
名前: 鈴木 花子
趣味の1つ目: 読書

loads() は、JSON文字列を解析して対応するPythonのデータ型に変換します。JSONオブジェクトはPythonの辞書(dict)に、JSON配列はPythonのリスト(list)になります。

注意点: JSON文字列の形式が正しくない場合(例:末尾のカンマ、不正な引用符など)、json.JSONDecodeError 例外が発生します。

3. json.dump():Pythonオブジェクト → JSONファイル

PythonオブジェクトをJSON形式に変換し、指定されたファイルオブジェクトに書き込みます。dumps() と似ていますが、出力先が文字列ではなくファイルである点が異なります。

import json

python_data = {
    "product_id": "P12345",
    "name": "高性能ノートPC",
    "price": 150000,
    "in_stock": True,
    "specs": {
        "cpu": "Core i7",
        "ram_gb": 16,
        "storage_gb": 512
    },
    "tags": ["PC", "高性能", "テレワーク"]
}

file_path = 'product_data.json'

# ファイルに書き込みモード ('w') でオープンし、エンコーディングをUTF-8に指定
# ensure_ascii=False と併用する場合、UTF-8指定が重要
with open(file_path, 'w', encoding='utf-8') as f:
    # PythonオブジェクトをファイルにJSON形式で書き込む
    json.dump(python_data, f, ensure_ascii=False, indent=2)

print(f"データが '{file_path}' に書き込まれました。")

# 書き込まれた内容を確認 (オプション)
with open(file_path, 'r', encoding='utf-8') as f:
    print("\n--- ファイルの内容 ---")
    print(f.read())

このコードを実行すると、カレントディレクトリに product_data.json というファイルが作成され、指定したデータが整形されたJSON形式で書き込まれます。

dump() 関数の第1引数はシリアライズするPythonオブジェクト、第2引数は書き込み先のファイルオブジェクトです。ensure_ascii, indent, sort_keys などの引数は dumps() と同様に使用できます。

ファイルを扱う際は、with open(...) 構文を使うのが一般的です。これにより、ファイルのクローズ処理が自動で行われ、リソースリークを防ぐことができます。また、特に日本語などの非ASCII文字を含むデータを扱う場合は、encoding='utf-8' を指定することが強く推奨されます。RFC 7159ではUTF-8が推奨デフォルトエンコーディングとされています。

4. json.load():JSONファイル → Pythonオブジェクト

JSON形式で記述されたファイルオブジェクトからデータを読み込み、Pythonオブジェクトに変換(デシリアライズ)します。loads() と似ていますが、入力元が文字列ではなくファイルである点が異なります。

import json

file_path = 'product_data.json' # 先ほど dump() で作成したファイル

# ファイルを読み込みモード ('r') でオープンし、エンコーディングをUTF-8に指定
try:
    with open(file_path, 'r', encoding='utf-8') as f:
        # ファイルからJSONデータを読み込み、Pythonオブジェクトに変換
        loaded_data = json.load(f)

    print("--- 読み込んだPythonオブジェクト ---")
    print(loaded_data)
    print("\n--- 型 ---")
    print(type(loaded_data))
    print("\n--- アクセス例 ---")
    print(f"商品名: {loaded_data['name']}")
    print(f"価格: {loaded_data['price']}")
    print(f"スペック(CPU): {loaded_data['specs']['cpu']}")

except FileNotFoundError:
    print(f"エラー: ファイル '{file_path}' が見つかりません。")
except json.JSONDecodeError as e:
    print(f"エラー: JSONファイルの形式が正しくありません。詳細: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

このコードは、先ほど dump() で作成した product_data.json ファイルを読み込み、その内容をPythonの辞書オブジェクト loaded_data に変換します。

load() 関数の第1引数には、読み込み対象のファイルオブジェクトを指定します。loads() と同様に、ファイルの内容が不正なJSON形式の場合 json.JSONDecodeError が発生します。ファイルが存在しない場合は FileNotFoundError が発生するため、適切なエラーハンドリング(try...except ブロック)を行うことが推奨されます。

PythonのオブジェクトとJSONの間でデータを変換する際、データ型は以下のように対応付けられます。

Python JSON 備考
dict object ({} で囲まれたキー/値ペア) JSONのキーは常に文字列である必要があります。dumps() はPython辞書のキーを文字列に変換します。
list, tuple array ([] で囲まれた値のリスト) loads() / load() はJSON配列をPythonの list に変換します。
str string (ダブルクォート "" で囲まれた文字列) JSONの文字列は常にダブルクォートで囲まれます。
int, float number (数値) Pythonの intfloat はJSONの数値に対応します。
True true (真偽値) Pythonの True は小文字の true になります。
False false (真偽値) Pythonの False は小文字の false になります。
None null (ヌル値) Pythonの None は小文字の null になります。
float('inf'), float('-inf') Infinity, -Infinity 標準のJSON仕様(RFC 7159)では非推奨ですが、Pythonの json モジュールはデフォルトで対応します (allow_nan=True がデフォルト)。相互運用性を高めるには allow_nan=False を指定し、これらの値を扱わないようにするのが安全です。
float('nan') NaN 上記同様、標準JSON仕様では非推奨です。

デフォルトでは、Pythonの標準的なデータ型(辞書、リスト、文字列、数値、真偽値、None)しかJSONにシリアライズできません。自作のクラスのインスタンスなどを直接 json.dumps()json.dump() に渡すと TypeError が発生します。

import json
from datetime import datetime

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

user = User("Alice", datetime(2024, 4, 1, 12, 30, 0))

try:
    json_string = json.dumps(user, indent=2)
    print(json_string)
except TypeError as e:
    print(f"シリアライズエラー: {e}") # => シリアライズエラー: Object of type User is not JSON serializable

これを解決するには、カスタムエンコーダ/デコーダを定義する必要があります。

カスタムエンコーダ(シリアライズ)

シリアライズできないオブジェクトをどのようにJSON形式に変換するかを定義します。主に2つの方法があります。

方法1: default 引数を使用する

dumps()dump()default 引数に、変換ロジックを実装した関数を指定します。この関数は、シリアライズできないオブジェクトを受け取り、JSONで表現可能な形式(辞書や文字列など)を返すか、TypeError を送出する必要があります。

import json
from datetime import datetime, date

class User:
    def __init__(self, name, registered_at):
        self.name = name
        self.registered_at = registered_at # datetimeオブジェクト

def custom_serializer(obj):
    if isinstance(obj, (datetime, date)):
        # datetimeオブジェクトやdateオブジェクトはISO 8601形式の文字列に変換
        return obj.isoformat()
    elif isinstance(obj, User):
        # Userオブジェクトは辞書に変換
        return {
            "name": obj.name,
            "registered_at": obj.registered_at.isoformat(), # datetimeも変換
            "_custom_type": "User" # デコード時に型を識別するための情報 (オプション)
        }
    # 他のシリアライズ不可能な型があればここに追加
    raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")

user = User("Bob", datetime(2024, 4, 2, 9, 0, 0))

# default引数に関数を指定
json_string = json.dumps(user, default=custom_serializer, indent=2, ensure_ascii=False)
print("--- Userオブジェクト (default使用) ---")
print(json_string)

# datetimeオブジェクト単体でも変換可能
now = datetime.now()
json_datetime = json.dumps(now, default=custom_serializer)
print("\n--- datetimeオブジェクト (default使用) ---")
print(json_datetime) # 例: "2025-04-02T06:31:12.123456" (ISO 8601形式)

出力例:

--- Userオブジェクト (default使用) ---
{
  "name": "Bob",
  "registered_at": "2024-04-02T09:00:00",
  "_custom_type": "User"
}

--- datetimeオブジェクト (default使用) ---
"2025-04-02T06:31:12.123456"

方法2: json.JSONEncoder をサブクラス化する

より複雑なロジックや再利用性を高めたい場合は、json.JSONEncoder を継承し、default() メソッドをオーバーライドします。

import json
from datetime import datetime, date

class User: # 再掲
    def __init__(self, name, registered_at):
        self.name = name
        self.registered_at = registered_at

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        elif isinstance(obj, User):
            return {
                "name": obj.name,
                "registered_at": obj.registered_at.isoformat(),
                "_custom_type": "User" # デコード用
            }
        # 他の型が必要なら追加
        # 親クラスのdefaultを呼び出してデフォルトのTypeErrorを発生させる
        return super().default(obj)

user = User("Charlie", datetime(2024, 1, 1, 0, 0, 0))

# cls引数にカスタムエンコーダークラスを指定
json_string = json.dumps(user, cls=CustomEncoder, indent=2, ensure_ascii=False)
print("--- Userオブジェクト (カスタムEncoder使用) ---")
print(json_string)

出力:

--- Userオブジェクト (カスタムEncoder使用) ---
{
  "name": "Charlie",
  "registered_at": "2024-01-01T00:00:00",
  "_custom_type": "User"
}

エラーハンドリング:json.JSONDecodeError

JSON文字列やファイルのパース中に形式が不正である場合に発生するのが json.JSONDecodeError です(これは ValueError のサブクラスです)。外部から受け取ったJSONデータを処理する際には、このエラーを捕捉する try...except ブロックを記述することが重要です。

import json

invalid_json_string = '{"name": "Alice", "age": 30,}' # 末尾に余分なカンマ

try:
    data = json.loads(invalid_json_string)
    print(data)
except json.JSONDecodeError as e:
    print(f"JSONデコードエラーが発生しました!")
    print(f"エラーメッセージ: {e.msg}")
    print(f"エラー発生箇所 (行): {e.lineno}")
    print(f"エラー発生箇所 (列): {e.colno}")
    # print(f"エラー箇所周辺のドキュメント: {e.doc[e.pos-10:e.pos+10]}") # エラー箇所周辺の確認
except Exception as e:
    print(f"予期せぬエラー: {e}")

出力:

JSONデコードエラーが発生しました!
エラーメッセージ: Expecting property name enclosed in double quotes
エラー発生箇所 (行): 1
エラー発生箇所 (列): 29

エラーオブジェクト (e) には、エラーメッセージ (msg)、エラーが発生した行番号 (lineno)、列番号 (colno) などの情報が含まれており、デバッグに役立ちます。

また、辞書として読み込んだ後のキーアクセスエラー (KeyError) なども考慮に入れる必要があります。

import json

json_string = '{"name": "Bob"}' # age キーが存在しない

try:
    data = json.loads(json_string)
    print(f"Name: {data['name']}")
    # 存在しないキーにアクセスしようとすると KeyError が発生
    print(f"Age: {data['age']}")
except json.JSONDecodeError as e:
    print(f"JSONデコードエラー: {e}")
except KeyError as e:
    print(f"キーエラー: キー '{e}' が見つかりません。")
except Exception as e:
    print(f"予期せぬエラー: {e}")

出力:

Name: Bob
キーエラー: キー 'age' が見つかりません。

パフォーマンスに関する考慮事項

Python標準の json モジュールは純粋なPythonで実装されており、多くの場合で十分な性能を発揮します。しかし、非常に大量のJSONデータを頻繁に処理する必要がある場合、C言語で実装されたサードパーティライブラリの方が高速な場合があります。

  • orjson: 非常に高速なJSONライブラリとして知られています。標準ライブラリと互換性のあるAPIを提供しつつ、速度を重視しています。
  • ujson (UltraJSON): こちらも高速なC実装のライブラリです。

これらのライブラリは pip install orjsonpip install ujson でインストールできます。ただし、標準ライブラリとは挙動が微妙に異なる場合があるため、導入する際はドキュメントを確認し、十分なテストを行うことが重要です。

パフォーマンス改善の一般的なヒント:

  • 不要な変換(例:JSON文字列 → Pythonオブジェクト → 別のJSON文字列)を避ける。
  • 巨大なJSONファイルを一度にメモリに読み込むのではなく、ストリーミング処理やジェネレータを利用する(標準ライブラリでは直接的なストリーミングAPIは限定的ですが、ijsonのようなライブラリが役立ちます)。
  • ネットワーク経由でJSONを取得する場合は、データ量を削減したり、キャッシュを活用したりする。
  • JSONデータ自体の構造を最適化する(ネストが深すぎないか、冗長なデータがないかなど)。

コマンドラインツール:python -m json.tool

json モジュールには、コマンドラインからJSONデータを整形(Pretty Print)したり、検証したりするためのツールが含まれています。

例えば、data.json というファイルの内容を整形して表示するには、ターミナルで以下のように実行します。

# Linux/macOS
cat data.json | python -m json.tool

# Windows (PowerShell)
Get-Content data.json | python -m json.tool

ファイルが不正なJSON形式の場合、エラーメッセージが表示されます。簡単なJSONの整形や検証に便利です。

特定のキーでソートして表示することも可能です。

cat data.json | python -m json.tool --sort-keys

Pythonの json ライブラリは、様々な場面で活用されています。

  • Web APIとの連携:
    現代のWebサービスの多くは、データをJSON形式で送受信するAPIを提供しています。Pythonの requests ライブラリなどを使ってAPIからデータを取得し、json.loads() (または response.json() メソッド) でPythonオブジェクトに変換して利用したり、逆にPythonで作成したデータを json.dumps() でJSON文字列に変換してAPIに送信したりします。
    import requests
    import json
    
    # 例: OpenWeatherMap API (架空のAPIキー)
    api_key = "YOUR_API_KEY"
    city = "Tokyo"
    url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric⟨=ja"
    
    try:
        response = requests.get(url)
        response.raise_for_status() # ステータスコードが200番台以外なら例外発生
    
        # requests の .json() メソッドは内部で json.loads() を呼んでいる
        weather_data = response.json()
    
        print(f"{weather_data['name']}の天気:")
        print(f"- 天気: {weather_data['weather'][0]['description']}")
        print(f"- 気温: {weather_data['main']['temp']}℃")
        print(f"- 湿度: {weather_data['main']['humidity']}%")
    
    except requests.exceptions.RequestException as e:
        print(f"APIリクエストエラー: {e}")
    except json.JSONDecodeError:
        print("APIレスポンスのJSON形式が不正です。")
    except KeyError as e:
        print(f"レスポンスに必要なキー '{e}' が見つかりません。")
    
  • 設定ファイル:
    アプリケーションの設定情報(データベース接続情報、APIキー、各種パラメータなど)をJSONファイルに記述し、起動時に json.load() で読み込んで利用する、といった使い方が一般的です。JSONは人間が読み書きしやすいため、設定ファイルの形式として適しています。
  • データの保存・交換:
    プログラムの処理結果や中間データを、一時的または永続的にファイルに保存する際にJSON形式が使われます。異なるプログラミング言語やシステム間でデータをやり取りする場合にも、JSONの汎用性の高さが活かされます。
  • ログデータ:
    構造化されたログデータをJSON形式で出力することがあります。これにより、ログ解析ツールでの処理が容易になります。

Pythonの標準ライブラリ json は、現代のデータ駆動型アプリケーション開発において非常に重要で便利なツールです。この記事では、以下の主要な機能と使い方を解説しました。

  • 基本的な変換: dumps(), loads(), dump(), load() を使ったPythonオブジェクトとJSON文字列/ファイルの相互変換。
  • データ型の対応: PythonとJSON間でのデータ型マッピング。
  • 整形とオプション: indent, sort_keys, ensure_ascii などの便利な引数。
  • カスタムオブジェクトの処理: default 引数や JSONEncoder サブクラスによるカスタムシリアライズ、object_hookJSONDecoder サブクラスによるカスタムデシリアライズ。
  • エラーハンドリング: JSONDecodeError の捕捉。
  • 応用: パフォーマンスに関するヒントやコマンドラインツールの紹介。
  • ユースケース: API連携、設定ファイル、データ交換など。

json モジュールを使いこなすことで、Pythonでのデータ処理の幅が大きく広がります。ぜひ、実際のプロジェクトで活用してみてください!

さらに詳しい情報が必要な場合は、Pythonの公式ドキュメントを参照することをお勧めします。
json — JSON エンコーダおよびデコーダ — Python ドキュメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です