はじめに: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文字列を読みやすくするためにインデントを設定します。通常は2
や4
が使われます。整数だけでなく文字列(例:"\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の int と float は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データを読み込む際に、特定の構造を持つJSONオブジェクトをカスタムPythonオブジェクトに変換します。これには loads()
や load()
の object_hook
引数を使用します。
object_hook
に指定する関数は、デコードされたJSONオブジェクト(Pythonの辞書)を受け取り、それをカスタムオブジェクトに変換して返すか、あるいはそのままの辞書を返します。
import json
from datetime import datetime
class User: # 再掲
def __init__(self, name, registered_at):
self.name = name
self.registered_at = registered_at
def __repr__(self): # 表示用
return f"User(name='{self.name}', registered_at={self.registered_at!r})"
def custom_decoder(dct):
# エンコード時に付与した識別子をチェック
if "_custom_type" in dct and dct["_custom_type"] == "User":
# Userオブジェクトを再構築
# datetime文字列もdatetimeオブジェクトに変換
return User(dct["name"], datetime.fromisoformat(dct["registered_at"]))
# 他のカスタム型があればここに追加
# datetime文字列単体などを変換したい場合は、キーが存在するかなどで判定
if "registered_at" in dct and isinstance(dct["registered_at"], str):
try:
# ISOフォーマットならdatetimeに変換してみる (これはUserの場合と被るので注意が必要)
# より堅牢にするには、専用のキーを用意するなど工夫が必要
# ここでは単純化のためコメントアウト
# dct["registered_at"] = datetime.fromisoformat(dct["registered_at"])
pass
except ValueError:
pass # 変換できなければそのまま
return dct # マッチしなければ元の辞書を返す
json_string_user = """
{
"name": "David",
"registered_at": "2023-12-25T10:30:00",
"_custom_type": "User"
}
"""
json_string_other = """
{
"item_id": 101,
"description": "通常の辞書データ"
}
"""
# object_hookに関数を指定してデコード
user_obj = json.loads(json_string_user, object_hook=custom_decoder)
other_obj = json.loads(json_string_other, object_hook=custom_decoder)
print("--- デコード結果 (User) ---")
print(user_obj)
print(type(user_obj))
print(f"登録日時オブジェクトの型: {type(user_obj.registered_at)}")
print("\n--- デコード結果 (Other) ---")
print(other_obj)
print(type(other_obj)) # => (custom_decoderで変換されなかった)
出力:
--- デコード結果 (User) ---
User(name='David', registered_at=datetime.datetime(2023, 12, 25, 10, 30))
<class '__main__.User'>
登録日時オブジェクトの型: <class 'datetime.datetime'>
--- デコード結果 (Other) ---
{'item_id': 101, 'description': '通常の辞書データ'}
<class 'dict'>
object_hook
はJSONオブジェクトがネストしている場合、内側のオブジェクトから順に呼び出されます。これにより、複雑な構造を持つJSONデータも適切にカスタムオブジェクトに変換できます。
カスタムエンコードと同様に、json.JSONDecoder
をサブクラス化し、object_hook
属性を持つデコーダを作成することも可能です。
高度な使い方とヒント
ユースケース:どんな時に使う?
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_hook
やJSONDecoder
サブクラスによるカスタムデシリアライズ。 - エラーハンドリング:
JSONDecodeError
の捕捉。 - 応用: パフォーマンスに関するヒントやコマンドラインツールの紹介。
- ユースケース: API連携、設定ファイル、データ交換など。
json
モジュールを使いこなすことで、Pythonでのデータ処理の幅が大きく広がります。ぜひ、実際のプロジェクトで活用してみてください!
さらに詳しい情報が必要な場合は、Pythonの公式ドキュメントを参照することをお勧めします。
json — JSON エンコーダおよびデコーダ — Python ドキュメント