プログラミング / 言語 / Python
Excel を JSON に変換する
— openpyxl / pandas × json モジュール
1. json.dumps の基本
Python オブジェクトを JSON 文字列に変換するには json.dumps() を使います。主要なオプションを押さえておきましょう。
import json
data = {
"name": "田中太郎",
"age": 30,
"active": True,
"score": None,
"tags": ["Python", "Excel"]
}
# ── 基本変換 ─────────────────────────────────────────────
s = json.dumps(data)
print(s)
# {"name": "田中太郎", "age": 30, ...}
# ── 日本語を ASCII エスケープしない ───────────────────────
s = json.dumps(data, ensure_ascii=False)
print(s)
# {"name": "田中太郎", "age": 30, "active": true, "score": null, ...}
# ── 整形して出力(インデントあり) ───────────────────────
s = json.dumps(data, ensure_ascii=False, indent=2)
print(s)
# {
# "name": "田中太郎",
# "age": 30,
# ...
# }
# ── キーをソートして出力 ─────────────────────────────────
s = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True)
# ── 区切り文字のカスタマイズ(compact 出力) ─────────────
s = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
# {"name":"田中太郎","age":30,...}
| オプション | デフォルト | 説明 |
|---|---|---|
ensure_ascii | True | False で日本語をそのまま出力 |
indent | None | 整数でインデントスペース数を指定 |
sort_keys | False | True でキーをアルファベット順にソート |
separators | (", ", ": ") | (",", ":") でスペースなしのコンパクト出力 |
default | None | 非対応型の変換関数を指定 |
2. Excel → dict(openpyxl)
openpyxl でシートを読み取り、ヘッダー行をキーとした dict のリストに変換します。
import openpyxl
def sheet_to_dicts(ws, header_row: int = 1) -> list[dict]:
"""
シートをヘッダー付き dict のリストに変換
- header_row: ヘッダーが何行目か(デフォルト 1 行目)
"""
rows = ws.iter_rows(values_only=True)
# ヘッダー行まで読み飛ばす
headers = None
for _ in range(header_row - 1):
next(rows, None)
headers = [str(h) if h is not None else f"col_{i}"
for i, h in enumerate(next(rows, []))]
# データ行を dict に変換
records = []
for row in rows:
if all(v is None for v in row):
continue # 空行はスキップ
record = dict(zip(headers, row))
records.append(record)
return records
# ── 使用例 ───────────────────────────────────────────────
wb = openpyxl.load_workbook("sales.xlsx", data_only=True)
ws = wb.active
records = sheet_to_dicts(ws)
print(f"読み取り: {len(records)} 件")
print(records[0])
# {'ID': 1, '名前': '田中太郎', '部署': '営業', '売上': 520000, '登録日': datetime(2026,...)}
wb.close()
datetime の扱い: openpyxl は日付セルを
datetime オブジェクトとして返します。json.dumps() は datetime を直接シリアライズできないため、後述のカスタムエンコーダが必要です。
3. Excel → JSON 文字列(openpyxl)
openpyxl で読み取った dict リストを json.dumps() で JSON 文字列に変換します。
import json
import openpyxl
from datetime import datetime, date
# ── カスタムエンコーダ(datetime / date 対応) ───────────
class ExcelEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%dT%H:%M:%S") # ISO 8601
if isinstance(obj, date):
return obj.strftime("%Y-%m-%d")
return super().default(obj)
# ── Excel を読み込む ─────────────────────────────────────
wb = openpyxl.load_workbook("sales.xlsx", data_only=True)
ws = wb.active
# ヘッダーを取得
headers = [cell.value for cell in ws[1]]
# データ行を dict のリストに変換
records = []
for row in ws.iter_rows(min_row=2, values_only=True):
if all(v is None for v in row):
continue
records.append(dict(zip(headers, row)))
wb.close()
# ── JSON 文字列に変換 ────────────────────────────────────
json_str = json.dumps(records, ensure_ascii=False, indent=2, cls=ExcelEncoder)
print(json_str[:300]) # 先頭 300 文字を確認
出力例
[
{
"ID": 1,
"名前": "田中太郎",
"部署": "営業",
"売上": 520000,
"登録日": "2026-01-15"
},
{
"ID": 2,
"名前": "鈴木花子",
...
}
]4. Excel → JSON(pandas)
pandas を使う方法はコードが簡潔です。DataFrame.to_json() が JSON 変換を一手に引き受けます。
import pandas as pd
import json
# ── Excel を DataFrame に読み込む ────────────────────────
df = pd.read_excel("sales.xlsx", engine="openpyxl")
print(df.head())
# ── to_json の主要な orient オプション ───────────────────
# orient='records' — [{列:値, ...}, ...] 形式(最もよく使う)
json_str = df.to_json(orient="records", force_ascii=False, indent=2)
print(json_str[:200])
# orient='index' — {行インデックス: {列:値, ...}, ...}
# orient='columns' — {列名: {行インデックス: 値, ...}, ...}
# orient='values' — [[値, 値, ...], ...] 配列形式
# orient='split' — {columns, index, data} を分離した形式
# orient='table' — JSON Table Schema に準拠
# ── 日付の扱い ───────────────────────────────────────────
# date_format='iso' で ISO 8601 文字列に変換
json_str = df.to_json(
orient="records",
force_ascii=False,
indent=2,
date_format="iso", # "iso" または "epoch"(Unix 時間)
default_handler=str # 未対応型は str に変換
)
# ── None / NaN の扱い ────────────────────────────────────
# pandas の NaN は JSON では null になる
# NaN を "N/A" に置換してから変換する場合
df_clean = df.fillna("N/A")
json_str = df_clean.to_json(orient="records", force_ascii=False, indent=2)
# ── JSON を dict/list として取得 ─────────────────────────
records = json.loads(json_str)
print(type(records)) #
print(len(records)) # 行数
5. カスタムエンコーダ(datetime 対応)
json.JSONEncoder を継承してカスタムエンコーダを作ると、datetime・Decimal・独自クラスなど標準では変換できない型に対応できます。
import json
from datetime import datetime, date, time
from decimal import Decimal
import openpyxl
# ── 汎用カスタムエンコーダ ───────────────────────────────
class UniversalEncoder(json.JSONEncoder):
"""openpyxl / Excel で遭遇する型をすべてカバーするエンコーダ"""
def default(self, obj):
# datetime(日時)→ ISO 8601 文字列
if isinstance(obj, datetime):
return obj.isoformat() # "2026-05-26T14:30:00"
# date(日付のみ)→ YYYY-MM-DD
if isinstance(obj, date):
return obj.strftime("%Y-%m-%d") # "2026-05-26"
# time(時刻のみ)→ HH:MM:SS
if isinstance(obj, time):
return obj.strftime("%H:%M:%S") # "14:30:00"
# Decimal → float(精度が必要なら str にする)
if isinstance(obj, Decimal):
return float(obj)
# bytes → base64 文字列
if isinstance(obj, (bytes, bytearray)):
import base64
return base64.b64encode(obj).decode("utf-8")
# その他は str に変換(フォールバック)
return str(obj)
# ── 使用例 ───────────────────────────────────────────────
wb = openpyxl.load_workbook("sample.xlsx", data_only=True)
ws = wb.active
headers = [c.value for c in ws[1]]
records = [dict(zip(headers, row)) for row in ws.iter_rows(min_row=2, values_only=True)
if any(v is not None for v in row)]
wb.close()
# cls=UniversalEncoder を渡すだけで OK
json_str = json.dumps(records, ensure_ascii=False, indent=2, cls=UniversalEncoder)
print(json_str)
# ── default 関数として渡す方法(クラス不要) ─────────────
def excel_default(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
raise TypeError(f"型 {type(obj)} はシリアライズできません")
json_str2 = json.dumps(records, ensure_ascii=False, indent=2, default=excel_default)
6. ネスト JSON を生成する
フラットな Excel データから、階層構造を持つネスト JSON を生成するパターンです。グループ集約・親子関係の構築に役立ちます。
import json
import openpyxl
from collections import defaultdict
# ── Excel: 部署・社員の売上データ ────────────────────────
# | 部署コード | 部署名 | 社員ID | 社員名 | 売上 |
# |-----------|--------|--------|--------|--------|
# | S01 | 営業 | 1 | 田中 | 520000 |
# | S01 | 営業 | 2 | 山田 | 390000 |
# | D01 | 開発 | 3 | 鈴木 | 480000 |
wb = openpyxl.load_workbook("dept_sales.xlsx", data_only=True)
ws = wb.active
headers = [c.value for c in ws[1]]
rows = [dict(zip(headers, r)) for r in ws.iter_rows(min_row=2, values_only=True)
if any(v is not None for v in r)]
wb.close()
# ── 部署コードでグループ化してネスト JSON を生成 ──────────
dept_map = defaultdict(lambda: {"部署名": "", "社員数": 0, "合計売上": 0, "社員": []})
for row in rows:
code = row["部署コード"]
dept_map[code]["部署名"] = row["部署名"]
dept_map[code]["合計売上"] += row["売上"] or 0
dept_map[code]["社員数"] += 1
dept_map[code]["社員"].append({
"社員ID": row["社員ID"],
"社員名": row["社員名"],
"売上": row["売上"]
})
# dict → list に変換してトップレベルに部署コードを追加
result = []
for code, dept in dept_map.items():
result.append({"部署コード": code, **dept})
json_str = json.dumps(result, ensure_ascii=False, indent=2)
print(json_str)
出力例
[
{
"部署コード": "S01",
"部署名": "営業",
"社員数": 2,
"合計売上": 910000,
"社員": [
{"社員ID": 1, "社員名": "田中", "売上": 520000},
{"社員ID": 2, "社員名": "山田", "売上": 390000}
]
},
{
"部署コード": "D01",
"部署名": "開発",
"社員数": 1,
"合計売上": 480000,
"社員": [
{"社員ID": 3, "社員名": "鈴木", "売上": 480000}
]
}
]7. JSON スキーマ設計のパターン
Excel → JSON の変換時によく使われる出力形式を比較します。用途に合わせて選びましょう。
| 形式 | 構造 | pandas orient | 向いている用途 |
|---|---|---|---|
| records | [{k:v}, ...] |
"records" |
API レスポンス・汎用データ交換(最もよく使う) |
| フラット配列 | [[v1,v2,...], ...] |
"values" |
軽量な転送・列順が確定している場合 |
| ネスト(グループ化) | [{key, children:[...]}] |
手動で生成 | 親子関係・階層データ・集計レポート |
| キー付きオブジェクト | {id: {k:v}, ...} |
"index" |
ID で素早くルックアップしたい場合 |
| JSON Lines | 1 行 1 JSON オブジェクト | lines=True |
大容量ログ・ストリーム処理・BigQuery 取込 |
import pandas as pd
df = pd.read_excel("sales.xlsx")
# records — [{列:値}, ...]
df.to_json("out_records.json", orient="records", force_ascii=False, indent=2)
# index — {0: {列:値}, 1: {...}, ...}
df.to_json("out_index.json", orient="index", force_ascii=False, indent=2)
# values — [[値, 値, ...], ...]
df.to_json("out_values.json", orient="values")
# JSON Lines — 1 行 1 JSON(大容量向け)
df.to_json("out_lines.jsonl", orient="records", lines=True, force_ascii=False)
# 特定の ID 列をキーにした辞書形式(手動)
import json
records = json.loads(df.to_json(orient="records", force_ascii=False))
keyed = {r["ID"]: r for r in records}
with open("out_keyed.json", "w", encoding="utf-8") as f:
json.dump(keyed, f, ensure_ascii=False, indent=2)
8. JSON ファイルへの保存
json.dump() でオブジェクトをファイルに直接書き出します。ファイル出力のベストプラクティスをまとめます。
import json
from pathlib import Path
from datetime import datetime
# ── 基本的なファイル保存 ─────────────────────────────────
data = [{"id": 1, "name": "田中", "created": datetime(2026, 5, 26)}]
# cls=UniversalEncoder を使って datetime を変換
class UniversalEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
output_path = Path("output.json")
with output_path.open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2, cls=UniversalEncoder)
print(f"保存完了: {output_path}")
# ── タイムスタンプ付きファイル名 ─────────────────────────
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
timestamped_path = Path(f"output_{ts}.json")
# ── 出力ディレクトリを自動作成 ───────────────────────────
out_dir = Path("json_output")
out_dir.mkdir(parents=True, exist_ok=True)
with (out_dir / "result.json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2, cls=UniversalEncoder)
# ── 既存ファイルに追記(JSON Lines 形式) ───────────────
new_record = {"id": 99, "name": "新規データ", "created": datetime.now()}
with open("output.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(new_record, ensure_ascii=False, cls=UniversalEncoder) + "\n")
# ── 大容量データを JSON Lines で保存(メモリ節約) ──────
def save_as_jsonlines(records, filepath: str, encoder=None):
"""レコードを 1 行 1 JSON の jsonlines 形式で保存"""
with open(filepath, "w", encoding="utf-8") as f:
for record in records:
line = json.dumps(record, ensure_ascii=False, cls=encoder)
f.write(line + "\n")
print(f"{len(records)} 件を {filepath} に保存しました")
JSON Lines(.jsonl)形式: 1 行に 1 つの JSON オブジェクトを書く形式で、大容量データのストリーム処理や BigQuery・Elasticsearch へのデータ取込に適しています。pandas の
to_json(lines=True) でも出力できます。
9. 実践スクリプト
Excel の複数シートを読み込み、フラット JSON・ネスト JSON・JSON Lines の 3 形式でまとめて出力するスクリプトです。
"""
Excel → JSON 実践スクリプト
- 複数シートを読み込んでフラット / ネスト / JSON Lines の 3 形式で出力
"""
import json
import openpyxl
import pandas as pd
from pathlib import Path
from datetime import datetime, date
from collections import defaultdict
# ── カスタムエンコーダ ────────────────────────────────────
class ExcelEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return str(obj)
def excel_to_records(filepath: str, sheet_name: str = None) -> list[dict]:
"""Excel シートを dict のリストに変換(openpyxl)"""
wb = openpyxl.load_workbook(filepath, data_only=True)
ws = wb[sheet_name] if sheet_name else wb.active
headers = [str(c.value or f"col_{i}") for i, c in enumerate(ws[1])]
records = []
for row in ws.iter_rows(min_row=2, values_only=True):
if all(v is None for v in row):
continue
records.append(dict(zip(headers, row)))
wb.close()
return records
def group_by_key(records: list[dict], group_key: str, child_keys: list[str]) -> list[dict]:
"""指定キーでグループ化してネスト JSON を生成"""
groups: dict = defaultdict(lambda: {group_key: None, "items": []})
for rec in records:
key_val = rec.get(group_key)
groups[key_val][group_key] = key_val
child = {k: rec[k] for k in child_keys if k in rec}
groups[key_val]["items"].append(child)
return list(groups.values())
def save_json(data, path: str, indent: int = 2):
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=indent, cls=ExcelEncoder)
print(f" 保存: {path} ({len(data)} 件)")
def save_jsonlines(records: list[dict], path: str):
with open(path, "w", encoding="utf-8") as f:
for rec in records:
f.write(json.dumps(rec, ensure_ascii=False, cls=ExcelEncoder) + "\n")
print(f" 保存: {path} ({len(records)} 件, JSON Lines)")
def main():
input_file = "sales_report.xlsx"
out_dir = Path("json_export")
out_dir.mkdir(exist_ok=True)
# 全シートを処理
wb = openpyxl.load_workbook(input_file)
print(f"シート一覧: {wb.sheetnames}")
wb.close()
all_records = []
for sheet in ["1月", "2月", "3月"]:
try:
records = excel_to_records(input_file, sheet)
for rec in records:
rec["シート"] = sheet
all_records.extend(records)
print(f" {sheet}: {len(records)} 件")
except KeyError:
print(f" {sheet}: シートが見つかりません(スキップ)")
print(f"\n合計 {len(all_records)} 件を読み込みました")
# ① フラット JSON(records 形式)
save_json(all_records, out_dir / "flat.json")
# ② ネスト JSON(部署でグループ化)
if all_records and "部署" in all_records[0]:
non_group_keys = [k for k in all_records[0] if k != "部署"]
nested = group_by_key(all_records, "部署", non_group_keys)
save_json(nested, out_dir / "nested_by_dept.json")
# ③ JSON Lines(大容量向け)
save_jsonlines(all_records, out_dir / "all_records.jsonl")
# ④ pandas でも出力(orient='records' + 日付 ISO 形式)
df = pd.concat([
pd.read_excel(input_file, sheet_name=s, engine="openpyxl")
for s in ["1月", "2月", "3月"]
if s in openpyxl.load_workbook(input_file).sheetnames
], ignore_index=True)
df.to_json(out_dir / "pandas_output.json",
orient="records", force_ascii=False,
indent=2, date_format="iso")
print(f" 保存: {out_dir}/pandas_output.json ({len(df)} 件, pandas)")
if __name__ == "__main__":
main()
実行例
シート一覧: ['1月', '2月', '3月', '集計']
1月: 31 件
2月: 28 件
3月: 31 件
合計 90 件を読み込みました
保存: json_export/flat.json (90 件)
保存: json_export/nested_by_dept.json (3 件)
保存: json_export/all_records.jsonl (90 件, JSON Lines)
保存: json_export/pandas_output.json (90 件, pandas)