プログラミング / 言語 / Python

JSON を読み込んで Excel に書き出す
— json モジュール × openpyxl / pandas

1. json モジュールの基本

Python 標準ライブラリの json モジュールは追加インストール不要で使えます。JSON(JavaScript Object Notation)は設定ファイル・API レスポンス・データ交換など、現代のシステムで広く使われるデータ形式です。

関数方向説明
json.loads(s)JSON文字列 → Python文字列を Python オブジェクトに変換
json.load(f)JSONファイル → Pythonファイルオブジェクトから読み取り
json.dumps(obj)Python → JSON文字列Python オブジェクトを JSON 文字列に変換
json.dump(obj, f)Python → JSONファイルファイルオブジェクトに書き込み
JSON 型Python 型
object {}dict
array []list
string "..."str
number(整数)int
number(小数)float
true / falseTrue / False
nullNone

2. 必要なライブラリのインストール

json は標準ライブラリのため追加インストール不要です。Excel 出力に openpyxl・pandas、HTTP リクエストに requests が必要な場合は pip でインストールします。

shell
# json は Python 標準ライブラリ — インストール不要

# Excel 出力用
pip install openpyxl pandas

# API からデータ取得する場合
pip install requests

3. 文字列から JSON を読み取る(json.loads)

API レスポンスや変数に格納された JSON 文字列を Python オブジェクトに変換するには json.loads() を使います。

python
import json

# ── 基本的な JSON 文字列の解析 ────────────────────────────
json_str = '{"name": "田中太郎", "age": 30, "active": true}'
data = json.loads(json_str)

print(type(data))           # 
print(data["name"])         # 田中太郎
print(data["age"])          # 30
print(data["active"])       # True

# ── 配列を含む JSON ───────────────────────────────────────
json_arr = '[{"id": 1, "score": 85}, {"id": 2, "score": 92}]'
items = json.loads(json_arr)

print(type(items))          # 
for item in items:
    print(item["id"], item["score"])

# ── ネストされた JSON ─────────────────────────────────────
json_nested = '''
{
  "company": "株式会社サンプル",
  "employees": [
    {"name": "田中", "dept": "営業", "salary": 450000},
    {"name": "鈴木", "dept": "開発", "salary": 520000}
  ],
  "address": {"city": "東京", "zip": "100-0001"}
}
'''
obj = json.loads(json_nested)
print(obj["company"])                   # 株式会社サンプル
print(obj["address"]["city"])           # 東京
for emp in obj["employees"]:
    print(emp["name"], emp["salary"])
出力例
田中 450000 鈴木 520000

4. ファイルから JSON を読み取る(json.load)

JSON ファイルを直接読み込むには json.load() を使います。open() と組み合わせて使うのが基本パターンです。

python
import json
from pathlib import Path

# ── 基本的なファイル読み込み ──────────────────────────────
with open("data.json", "r", encoding="utf-8") as f:
    data = json.load(f)

print(type(data))           # dict または list(JSON の最上位構造による)

# ── Path を使った読み込み ─────────────────────────────────
path = Path("data/employees.json")
with path.open(encoding="utf-8") as f:
    employees = json.load(f)

# ── 読み込んだデータを操作 ───────────────────────────────
# data.json の内容例:
# [
#   {"id": 1, "name": "田中", "dept": "営業", "sales": 520000},
#   {"id": 2, "name": "鈴木", "dept": "開発", "sales": 380000}
# ]
for emp in employees:
    print(f"{emp['id']}: {emp['name']} ({emp['dept']}) — {emp['sales']:,}円")

# ── エラーハンドリング ────────────────────────────────────
try:
    with open("config.json", "r", encoding="utf-8") as f:
        config = json.load(f)
except FileNotFoundError:
    print("ファイルが見つかりません")
    config = {}
except json.JSONDecodeError as e:
    print(f"JSON の解析エラー: {e}")
    config = {}
文字コードに注意: 日本語を含む JSON ファイルは encoding="utf-8" を明示するのが確実です。Windows 環境では UTF-8-BOM 形式のファイルもあるため、その場合は encoding="utf-8-sig" を使います。

5. ネスト構造の展開

実際の JSON データはネスト(入れ子)構造になっていることが多く、Excel に書き出す前にフラット(平坦)な形式に展開する必要があります。

python
import json

# ── ネスト JSON のサンプル ────────────────────────────────
json_str = '''
[
  {
    "id": 1,
    "name": "田中太郎",
    "contact": {"email": "tanaka@example.com", "tel": "090-1234-5678"},
    "scores": [85, 92, 78]
  },
  {
    "id": 2,
    "name": "鈴木花子",
    "contact": {"email": "suzuki@example.com", "tel": "090-8765-4321"},
    "scores": [90, 88, 95]
  }
]
'''
data = json.loads(json_str)

# ── 手動でフラット化 ──────────────────────────────────────
def flatten_record(record: dict) -> dict:
    """1 レコードをフラットな dict に変換"""
    flat = {}
    for key, val in record.items():
        if isinstance(val, dict):
            # ネスト dict をドット記法でキー展開
            for sub_key, sub_val in val.items():
                flat[f"{key}.{sub_key}"] = sub_val
        elif isinstance(val, list):
            # リストはカンマ区切りの文字列に変換(または列ごとに展開)
            flat[key] = ", ".join(str(v) for v in val)
        else:
            flat[key] = val
    return flat

flat_data = [flatten_record(r) for r in data]
for row in flat_data:
    print(row)
# {'id': 1, 'name': '田中太郎',
#  'contact.email': 'tanaka@example.com', 'contact.tel': '090-1234-5678',
#  'scores': '85, 92, 78'}

# ── リストを別列に展開する場合 ──────────────────────────
def flatten_with_list_cols(record: dict, list_key: str) -> dict:
    flat = flatten_record(record)
    # scores を score_1, score_2, ... に展開
    scores = record.get(list_key, [])
    flat.pop(list_key, None)
    for i, val in enumerate(scores, start=1):
        flat[f"{list_key}_{i}"] = val
    return flat

expanded = [flatten_with_list_cols(r, "scores") for r in data]
for row in expanded:
    print(row)
出力例(flatten_with_list_cols)
{'id': 1, 'name': '田中太郎', 'contact.email': 'tanaka@example.com', 'contact.tel': '090-1234-5678', 'scores_1': 85, 'scores_2': 92, 'scores_3': 78} {'id': 2, 'name': '鈴木花子', 'contact.email': 'suzuki@example.com', 'contact.tel': '090-8765-4321', 'scores_1': 90, 'scores_2': 88, 'scores_3': 95}

6. pandas.json_normalize でフラット化

ネスト JSON のフラット化は pandas.json_normalize() を使うと自動で行えます。手動の展開よりも簡潔に書けます。

python
import json
import pandas as pd

json_str = '''
[
  {
    "id": 1, "name": "田中太郎",
    "contact": {"email": "tanaka@example.com", "tel": "090-1234-5678"},
    "dept": {"name": "営業", "code": "S01"}
  },
  {
    "id": 2, "name": "鈴木花子",
    "contact": {"email": "suzuki@example.com", "tel": "090-8765-4321"},
    "dept": {"name": "開発", "code": "D01"}
  }
]
'''
data = json.loads(json_str)

# ── 基本的な json_normalize ──────────────────────────────
df = pd.json_normalize(data)
print(df.columns.tolist())
# ['id', 'name', 'contact.email', 'contact.tel', 'dept.name', 'dept.code']
print(df)

# ── セパレータを変更 ─────────────────────────────────────
df2 = pd.json_normalize(data, sep="_")
print(df2.columns.tolist())
# ['id', 'name', 'contact_email', 'contact_tel', 'dept_name', 'dept_code']

# ── ネストが深い場合 max_level で展開深度を制御 ─────────
df3 = pd.json_normalize(data, max_level=1)

# ── 配列フィールドを record_path で展開 ─────────────────
json_with_arr = '''
[
  {"store": "東京店", "items": [{"name": "A", "qty": 10}, {"name": "B", "qty": 5}]},
  {"store": "大阪店", "items": [{"name": "C", "qty": 8}]}
]
'''
data2 = json.loads(json_with_arr)

# items 配列を行に展開し、親の store を各行に結合
df4 = pd.json_normalize(data2, record_path="items", meta=["store"])
print(df4)
#   name  qty store
# 0    A   10  東京店
# 1    B    5  東京店
# 2    C    8  大阪店

7. JSON → Excel(openpyxl)

openpyxl を使って JSON データを直接 Excel に書き出す方法です。書式設定が必要な場合はこちらのアプローチが柔軟です。

python
import json
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment

# ── JSON ファイルを読み込む ───────────────────────────────
with open("employees.json", "r", encoding="utf-8") as f:
    records = json.load(f)
# 例: [{"id":1,"name":"田中","dept":"営業","salary":450000}, ...]

# ── openpyxl でブックを作成 ──────────────────────────────
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "社員データ"

# ── ヘッダーを JSON キーから自動生成 ─────────────────────
if records:
    headers = list(records[0].keys())
    ws.append(headers)

    # ヘッダー書式
    header_fill = PatternFill(fill_type="solid", start_color="1F3864", end_color="1F3864")
    header_font = Font(bold=True, color="FFFFFF")
    for cell in ws[1]:
        cell.fill      = header_fill
        cell.font      = header_font
        cell.alignment = Alignment(horizontal="center")

# ── データ行を書き出す ───────────────────────────────────
for record in records:
    # headers の順番に値を並べる(キーが欠けている場合は None)
    row = [record.get(h) for h in headers]
    ws.append(row)

# ── 列幅を自動調整 ───────────────────────────────────────
for col in ws.columns:
    max_len = max((len(str(c.value or "")) for c in col), default=8)
    ws.column_dimensions[col[0].column_letter].width = max(max_len * 1.2, 10)

# ── 保存 ─────────────────────────────────────────────────
wb.save("employees_output.xlsx")
print(f"Excel に書き出しました: {len(records)} 件")
キー順序の保証: Python 3.7 以降、dict はキーの挿入順序を保持します。records[0].keys() でヘッダーを生成するとデータ順に列が並びます。列順を指定したい場合は明示的に headers = ["id", "name", "dept", "salary"] と定義しましょう。

8. JSON → Excel(pandas)

pandas を使う方法はコードが簡潔で、pd.read_json() で JSON を直接 DataFrame に変換できます。

python
import json
import pandas as pd

# ── 方法① pd.read_json でファイルから直接読み込む ─────────
df = pd.read_json("employees.json", encoding="utf-8")
print(df.head())

# ── 方法② json.load + DataFrame 変換 ─────────────────────
with open("employees.json", "r", encoding="utf-8") as f:
    records = json.load(f)

df = pd.DataFrame(records)

# ── 方法③ json 文字列から直接 ────────────────────────────
import io
json_str = '[{"id":1,"name":"田中","salary":450000}]'
df = pd.read_json(io.StringIO(json_str))

# ── ネスト JSON は json_normalize でフラット化してから ──────
from pandas import json_normalize
with open("nested.json", "r", encoding="utf-8") as f:
    nested = json.load(f)
df = pd.json_normalize(nested, sep="_")

# ── DataFrame を Excel に書き出す ─────────────────────────
df.to_excel("output.xlsx", index=False, sheet_name="データ")

# ── 書式付きで出力(ExcelWriter を使う) ─────────────────
from openpyxl.styles import Font, PatternFill, Alignment

with pd.ExcelWriter("output_styled.xlsx", engine="openpyxl") as writer:
    df.to_excel(writer, index=False, sheet_name="社員データ")
    wb = writer.book
    ws = writer.sheets["社員データ"]

    header_fill = PatternFill(fill_type="solid", start_color="1F3864", end_color="1F3864")
    for cell in ws[1]:
        cell.fill      = header_fill
        cell.font      = Font(bold=True, color="FFFFFF")
        cell.alignment = Alignment(horizontal="center")

    for col in ws.columns:
        ws.column_dimensions[col[0].column_letter].width = 16

print("Excel に書き出しました")

9. API レスポンスを Excel に保存

Web API から取得した JSON レスポンスを Excel に保存する実践的なパターンです。requests ライブラリと組み合わせて使います。

python
import requests
import pandas as pd
from datetime import datetime

# ── API から JSON を取得 ──────────────────────────────────
# 例: REST API(公開サンプル)
url = "https://jsonplaceholder.typicode.com/users"
response = requests.get(url, timeout=10)
response.raise_for_status()        # 4xx/5xx でエラーを発生させる

data = response.json()             # レスポンスを dict/list に変換
print(f"取得件数: {len(data)}")

# ── ネストを展開して DataFrame に ────────────────────────
df = pd.json_normalize(
    data,
    sep="_",
    meta=["id", "name", "email", "phone", "website",
          ["address", "city"], ["company", "name"]]
)

# 必要な列だけ選択・列名を日本語にリネーム
df = df[["id", "name", "email", "phone", "address_city", "company_name"]]
df.columns = ["ID", "氏名", "メール", "電話", "都市", "会社名"]

# ── Excel に書き出す ─────────────────────────────────────
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"api_users_{timestamp}.xlsx"

with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
    df.to_excel(writer, index=False, sheet_name="ユーザー一覧")
    ws = writer.sheets["ユーザー一覧"]

    # ヘッダースタイル
    from openpyxl.styles import Font, PatternFill, Alignment
    for cell in ws[1]:
        cell.fill = PatternFill(fill_type="solid",
                                start_color="2E4057", end_color="2E4057")
        cell.font = Font(bold=True, color="FFFFFF")
        cell.alignment = Alignment(horizontal="center")

    # 列幅調整
    col_widths = [6, 18, 30, 16, 14, 22]
    for i, width in enumerate(col_widths, start=1):
        ws.column_dimensions[ws.cell(1, i).column_letter].width = width

print(f"保存完了: {output_path}")
本番 API を使う際の注意: レート制限・認証トークン・タイムアウト設定を適切に行いましょう。response.raise_for_status() で HTTP エラーを検出し、try/except requests.RequestException で通信エラーをキャッチするのがベストプラクティスです。

10. 実践スクリプト

複数の JSON ファイルを読み込んで結合し、書式付き Excel レポートを生成するスクリプトです。

python
"""
JSON → Excel 実践スクリプト
- ディレクトリ内の JSON ファイルを読み込んで結合し、書式付き Excel を生成
"""

import json
import openpyxl
import pandas as pd
from pathlib import Path
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment


HEADER_FILL = PatternFill(fill_type="solid", start_color="1F3864", end_color="1F3864")
HEADER_FONT = Font(bold=True, color="FFFFFF", size=11)
EVEN_FILL   = PatternFill(fill_type="solid", start_color="D9E1F2", end_color="D9E1F2")
THIN        = Side(border_style="thin", color="CCCCCC")
ALL_BORDER  = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)


def load_json_files(directory: str) -> list[dict]:
    """指定ディレクトリ内の全 JSON ファイルを読み込んで結合"""
    records = []
    dir_path = Path(directory)
    for json_file in sorted(dir_path.glob("*.json")):
        with json_file.open(encoding="utf-8") as f:
            data = json.load(f)
        if isinstance(data, list):
            records.extend(data)
        elif isinstance(data, dict):
            records.append(data)
        print(f"  読み込み: {json_file.name} ({len(data) if isinstance(data, list) else 1} 件)")
    return records


def records_to_df(records: list[dict]) -> pd.DataFrame:
    """レコードリストを DataFrame に変換(ネストは json_normalize で展開)"""
    df = pd.json_normalize(records, sep="_")
    return df


def write_styled_excel(df: pd.DataFrame, output_path: str, sheet_name: str = "データ"):
    """書式付き Excel を出力"""
    with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
        df.to_excel(writer, sheet_name=sheet_name, index=False)

    # 書式適用
    wb = openpyxl.load_workbook(output_path)
    ws = wb[sheet_name]
    ws.freeze_panes = "A2"

    # ヘッダー
    for cell in ws[1]:
        cell.fill      = HEADER_FILL
        cell.font      = HEADER_FONT
        cell.alignment = Alignment(horizontal="center", vertical="center")
        cell.border    = ALL_BORDER
    ws.row_dimensions[1].height = 22

    # データ行
    for r_idx, row in enumerate(ws.iter_rows(min_row=2), start=2):
        for cell in row:
            cell.border    = ALL_BORDER
            cell.alignment = Alignment(
                horizontal="right" if isinstance(cell.value, (int, float)) else "left",
                vertical="center"
            )
            if r_idx % 2 == 0:
                cell.fill = EVEN_FILL

    # 列幅自動調整
    for col in ws.columns:
        max_len = max((len(str(c.value or "")) for c in col), default=8)
        ws.column_dimensions[col[0].column_letter].width = min(max_len * 1.3 + 2, 40)

    wb.save(output_path)
    wb.close()


def main():
    json_dir = "json_data"       # JSON ファイルが入ったディレクトリ
    output   = "combined_output.xlsx"

    print(f"JSON ディレクトリ: {json_dir}")
    records = load_json_files(json_dir)
    print(f"\n合計 {len(records)} 件のレコードを読み込みました")

    if not records:
        print("データがありません")
        return

    df = records_to_df(records)
    print(f"DataFrame 形状: {df.shape[0]}行 × {df.shape[1]}列")
    print(f"列名: {df.columns.tolist()}")

    write_styled_excel(df, output, sheet_name="JSON結合データ")
    print(f"\nExcel を保存しました: {output}")


if __name__ == "__main__":
    main()
次のステップ: Excel データを JSON に変換して出力する方法は PART 09「Excel を JSON に変換する」で解説します。json.dumps のカスタムエンコーダや datetime 対応、ネスト JSON の生成まで詳しく説明します。