Python で JSON を CSV に変換する方法
無料の JSONからCSVへの変換ツール をブラウザで直接使用 — インストール不要。
JSONからCSVへの変換ツール をオンラインで試す →ほぼすべてのデータパイプラインは、同じステップに突き当たります。API が JSON を返すのに、 次の処理 — スプレッドシート、インポートスクリプト、Redshift の COPY コマンド — は CSV を必要としています。 Python で JSON を CSV に変換するのは簡単に見えますが、 ネストされたオブジェクト、キーの不一致、特別な処理が必要な日時値に直面すると話が変わります。 Python には2つの確かなアプローチがあります。依存ゼロのスクリプト向けの組み込み json + csv モジュール、そしてネストのフラット化や大規模データセット向けの pandas — またはコードなしで素早く1回だけ変換したい場合は オンライン JSON to CSV コンバーター も使えます。 このガイドでは両方のアプローチをエンドツーエンドで解説し、実行可能な Python 3.8+ のサンプルコードも紹介します。
- ✓csv.DictWriter は依存ゼロでディクショナリのリストを CSV に変換します — json.load() でパースし、writeheader() + writerows() を呼ぶだけ。
- ✓Windows での空白行を防ぐため、CSV ファイルは常に newline="" を指定して開いてください。
- ✓pd.json_normalize() はネストされた JSON を to_csv() 呼び出し前にフラットな DataFrame に変換します — 多段階のネストも自動処理。
- ✓DataFrame.to_csv() には index=False を渡してください — 指定しないと pandas が不要な行番号列を書き込みます。
- ✓500 MB を超えるファイルには、ijson によるストリーミング JSON パースと csv.DictWriter を組み合わせてメモリ使用量を一定に保ちます。
JSON から CSV への変換とは?
JSON から CSV への変換は、JSON オブジェクトの配列を表形式に変換するものです。 各オブジェクトが1行になり、各キーが列ヘッダーになります。JSON は階層的で — オブジェクトは任意の深さにネストできます。CSV はフラットで — すべての値が行と列のグリッドに収まります。 すべてのオブジェクトが同じトップレベルのキーセットを共有していれば、変換はきれいに行えます。 ネストされたオブジェクト、配列、キーの不一致があると話が複雑になります。 生データは同一のまま — 構造だけが変わります。
[{"order_id":"ord_91a3","total":149.99,"status":"shipped"},
{"order_id":"ord_b7f2","total":34.50,"status":"pending"}]order_id,total,status ord_91a3,149.99,shipped ord_b7f2,34.50,pending
csv.DictWriter — pandas なしで JSON を CSV に変換する
csv モジュールは すべての Python インストールに含まれています。pip install も 仮想環境の設定も不要です。 csv.DictWriter はディクショナリのリストを受け取り、各ディクショナリを CSV の1行として書き込み、 dict のキーを列ヘッダーにマッピングします。 fieldnames パラメータは列の順序と含めるキーの両方を制御します。
import json
import csv
# サンプル JSON データ — 注文オブジェクトの配列
json_string = """
[
{"order_id": "ord_91a3", "product": "Wireless Keyboard", "quantity": 2, "unit_price": 74.99},
{"order_id": "ord_b7f2", "product": "USB-C Hub", "quantity": 1, "unit_price": 34.50},
{"order_id": "ord_c4e8", "product": "Monitor Stand", "quantity": 3, "unit_price": 29.95}
]
"""
records = json.loads(json_string)
with open("orders.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
# orders.csv:
# order_id,product,quantity,unit_price
# ord_91a3,Wireless Keyboard,2,74.99
# ord_b7f2,USB-C Hub,1,34.50
# ord_c4e8,Monitor Stand,3,29.95open() のnewline="" 引数は Windows では省略できません。指定しないと二重キャリッジリターンが発生し — Excel でデータ行の間に空白行として表示されます。macOS と Linux では無害なので、 常に含めるようにしましょう。
上のコードは文字列に対して json.loads() を使っています。ファイルハンドルから読み込む場合は json.load() (末尾の s なし)を使います。 これはよくある混乱のポイントです — 一方は文字列を読み込み、もう一方はファイルオブジェクトを読み込みます。
import json
import csv
with open("server_metrics.json", encoding="utf-8") as jf:
metrics = json.load(jf) # ファイルオブジェクトには json.load() を使用
# 明示的な fieldnames で列の順序を制御
columns = ["timestamp", "hostname", "cpu_percent", "memory_mb", "disk_io_ops"]
with open("server_metrics.csv", "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=columns, extrasaction="ignore")
writer.writeheader()
writer.writerows(metrics)
# 指定した5列だけが、その順序通りに出力されるextrasaction="ignore" を設定すると、fieldnames リストにないキーを持つ dict が来ても黙って無視します。 デフォルトは "raise" で、 予期しないキーがある dict に対して ValueError を投げます。予期しない状況への許容度に合わせて選んでください。
csv.DictWriter と csv.writer の違い:DictWriter は dict のキーを列の位置に自動でマッピングします。csv.writer は 生のリストを行として書き込み — 列の順序は自分で管理します。JSON のレコードはすでにディクショナリなので、 JSON から CSV への変換にはほぼ常に DictWriter が正しい選択です。Python の csv モジュールには3つの名前付きダイアレクトが付属しています。 excel (カンマ区切り、CRLF 行末 — デフォルト)、 excel-tab (タブ区切り、CRLF 行末)、そして unix (LF 行末、数値以外のすべてのフィールドをクォート)です。ダイアレクト名を dialect 引数として csv.DictWriter に渡します。 ターゲットシステムが特殊なクォートやデリミタのルールを持つ場合は、 csv.register_dialect() でカスタムダイアレクトを定義することもできます。ほとんどの JSON から CSV への変換では excel ダイアレクトで問題ありませんが、 awk や sort などの POSIX ツールで処理するファイルを書き込む場合は unix に切り替えてください。
非標準型の処理:datetime、UUID、Decimal
API からの JSON には日付が ISO 文字列として、UUID がハイフン区切り文字列として、 金額がフロートとして含まれることがよくあります。これらを処理のために Python オブジェクトに変換してから CSV に書き込む場合、文字列に戻す必要があります。 csv モジュールはすべての値に str() を呼び出すので、ほとんどの型はそのまま動きます。しかし datetime オブジェクトはデフォルトの文字列表現が読みにくく、 Decimal 値は指数表記を避けるために明示的なフォーマットが必要です。
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID
# Python 型を持つ解析済み API レスポンスをシミュレート
transactions = [
{
"txn_id": UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
"created_at": datetime(2026, 3, 15, 9, 30, 0, tzinfo=timezone.utc),
"amount": Decimal("1249.99"),
"currency": "USD",
"merchant": "CloudHost Inc.",
},
{
"txn_id": UUID("b2c3d4e5-f6a7-8901-bcde-f12345678901"),
"created_at": datetime(2026, 3, 15, 14, 12, 0, tzinfo=timezone.utc),
"amount": Decimal("87.50"),
"currency": "EUR",
"merchant": "DataSync GmbH",
},
]
def prepare_row(record: dict) -> dict:
"""非文字列型を CSV に適した文字列に変換する。"""
return {
"txn_id": str(record["txn_id"]),
"created_at": record["created_at"].isoformat(),
"amount": f"{record['amount']:.2f}",
"currency": record["currency"],
"merchant": record["merchant"],
}
with open("transactions.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["txn_id", "created_at", "amount", "currency", "merchant"])
writer.writeheader()
for txn in transactions:
writer.writerow(prepare_row(txn))
# transactions.csv:
# txn_id,created_at,amount,currency,merchant
# a1b2c3d4-e5f6-7890-abcd-ef1234567890,2026-03-15T09:30:00+00:00,1249.99,USD,CloudHost Inc.
# b2c3d4e5-f6a7-8901-bcde-f12345678901,2026-03-15T14:12:00+00:00,87.50,EUR,DataSync GmbHprepare_row() 関数はここでの正しいアプローチです。 csv.DictWriter にカスタム型を教えようとするのではなく、書き込む前に各レコードを文字列に正規化します。 datetime オブジェクトには str() に頼るより.isoformat() を明示的に呼ぶ方が好ましいです — 出力フォーマットが予測しやすく、下流のパーサーも ISO 8601 を確実に処理できます。
Decimal 値をフォーマットせずに渡すと、 非常に小さいまたは大きな数値が指数表記(例:1.5E+7)で表示される場合があります。 財務データを CSV に書き込む場合は、f"{value:.2f}" のような明示的な f 文字列で Decimal を常にフォーマットしてください。多くのカスタム型を持つパイプラインでの代替パターンは、 json.JSONEncoder を拡張することです。 サブクラス化して default() メソッドをオーバーライドし、各カスタム型に対して JSON シリアライズ可能な値を返すようにしてから、 サブクラスを json.dumps() のcls 引数に渡します。カスタムエンコーダーを通して再エンコードしてから CSV に書き込むことで、 行ごとの prepare_row() 呼び出しなしに全型を一括で正規化できます。上記の prepare_row() パターンはスポット的なスクリプトには簡潔ですが、同じカスタム型を持つドメインモデルが 多くのパイプラインステージやマイクロサービスで共有される場合は JSONEncoder サブクラスの方がスケールします。
csv.DictWriter パラメータリファレンス
完全なコンストラクタシグネチャは csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams) です。ほとんどはデフォルトが適切です。実際に変更するのは fieldnames、 delimiter、 そして extrasaction くらいです。
pandas — DataFrame で JSON を CSV に変換する
すでに pandas を多用しているコードベースで作業している場合、またはネストされたオブジェクトを フラット化する必要がある場合、pandas アプローチは stdlib バージョンよりもはるかにコードが少なくなります。 トレードオフは pandas が約 30 MB の依存関係である点です。使い捨てスクリプトならそれで構いません。 本番環境に出荷する Docker イメージなら、stdlib アプローチの方が軽量です。
import pandas as pd
# JSON 配列を直接 DataFrame に読み込む
df = pd.read_json("warehouse_inventory.json")
# CSV に書き込む — index=False で自動生成の行番号を防ぐ
df.to_csv("warehouse_inventory.csv", index=False)
# 以上。2行だけ。pandas は列の型を自動推論する。index=False フラグは毎回調べてしまうものの一つです。指定しないと、pandas が CSV の最初の列として0, 1, 2, ... の列を書き込みます。それを必要とする人はほとんどいません。
json_normalize でネストされた JSON をフラット化する
実際の API レスポンスがフラットであることはほとんどありません。注文には配送先住所が含まれ、 ユーザーにはネストされた設定が含まれ、テレメトリイベントにはネストされたメタデータが含まれます。 pd.json_normalize() はネストされたディクショナリを走査し、ドット区切りの名前を持つ列にフラット化します。
import json
import pandas as pd
api_response = """
[
{
"order_id": "ord_91a3",
"placed_at": "2026-03-15T09:30:00Z",
"customer": {
"name": "Sarah Chen",
"email": "s.chen@example.com",
"tier": "premium"
},
"shipping": {
"method": "express",
"address": {
"city": "Portland",
"state": "OR",
"zip": "97201"
}
},
"total": 299.95
},
{
"order_id": "ord_b7f2",
"placed_at": "2026-03-15T14:12:00Z",
"customer": {
"name": "James Park",
"email": "j.park@example.com",
"tier": "standard"
},
"shipping": {
"method": "standard",
"address": {
"city": "Austin",
"state": "TX",
"zip": "73301"
}
},
"total": 87.50
}
]
"""
orders = json.loads(api_response)
# json_normalize はネストされた dict をフラット化 — sep で区切り文字を制御
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)
# 結果の列:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, totalsep="_" パラメータはネストされたキー名の結合方法を制御します。デフォルトは "." で、customer.name のような列名になります。列名のドットは SQL インポートや一部のスプレッドシートの数式でトラブルを起こすため、 アンダースコアを好んで使います。
ネストされたキーの下にレコード配列をラップしている API レスポンスには、 record_path パラメータを使います。レスポンスが {"data": {"orders": [...]}} の形の場合、 record_path=["data", "orders"] を渡してリストにナビゲートします。オプションの meta パラメータを使うと、ネストされたレコードと一緒に親レベルのフィールドを取り込めます — レスポンスに含まれるトップレベルのページネーション情報(ページ番号、合計件数)を すべての行の列として持ちたい場合に便利です。 record_path と meta を組み合わせれば、カスタム前処理なしに現実世界のほとんどのネストされた API レスポンスの形に対応できます。
DataFrame.to_csv() パラメータリファレンス
DataFrame.to_csv() には 20 以上のパラメータがあります。JSON から CSV へのワークフローで重要なものを紹介します。
import pandas as pd
df = pd.read_json("telemetry_events.json")
# 明示的なエンコーディングと欠損値の処理を含む TSV 出力
df.to_csv(
"telemetry_events.tsv",
sep="\t",
index=False,
encoding="utf-8",
na_rep="NULL",
columns=["event_id", "timestamp", "source", "severity", "message"],
)
# シェルスクリプトでパイプするために stdout に書き込む
print(df.to_csv(index=False))
# 文字列として返す(ファイルは書き込まれない)
csv_string = df.to_csv(index=False)
print(len(csv_string), "文字")ファイルと API レスポンスから JSON を CSV に変換する
最もよくある実際のシナリオは2つです。ディスク上のファイルから JSON を読み込んで変換するか、 HTTP API から JSON を取得して結果を CSV として保存するかです。開発中はエラー処理なしで済ませられますが、 本番環境ではそれが午前2時のアラートになります。ファイルが存在しない可能性があり、API が JSON の代わりに 4xx または 5xx ステータスコードを返すことがあり、レスポンスボディが配列ではなくエラーオブジェクトの場合があり、 ネットワークタイムアウトによって JSON が切り詰められることもあります。以下のパターンはこれらすべてを 明示的に処理し、エラーを stderr にログし、呼び出し元がゼロ行の出力を検出してアラートできるよう行数を返します。
ディスク上のファイル — 読み込み、変換、保存
import json
import csv
import sys
def json_file_to_csv(input_path: str, output_path: str) -> int:
"""オブジェクトの配列を含む JSON ファイルを CSV に変換する。
書き込んだ行数を返す。
"""
try:
with open(input_path, encoding="utf-8") as jf:
data = json.load(jf)
except FileNotFoundError:
print(f"エラー: {input_path} が見つかりません", file=sys.stderr)
return 0
except json.JSONDecodeError as exc:
print(f"エラー: {input_path} の JSON が不正です: {exc.msg} ({exc.lineno} 行目)", file=sys.stderr)
return 0
if not isinstance(data, list) or not data:
print(f"エラー: {input_path} に空でない JSON 配列が必要です", file=sys.stderr)
return 0
# 全レコードのユニークなキーを収集 — スキーマが一致しない場合に対応
all_keys: list[str] = []
seen: set[str] = set()
for record in data:
for key in record:
if key not in seen:
all_keys.append(key)
seen.add(key)
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=all_keys, restval="", extrasaction="ignore")
writer.writeheader()
writer.writerows(data)
return len(data)
rows = json_file_to_csv("deploy_logs.json", "deploy_logs.csv")
print(f"{rows} 行を deploy_logs.csv に書き込みました")HTTP API レスポンス — 取得して変換する
import json
import csv
import urllib.request
import urllib.error
def api_response_to_csv(url: str, output_path: str) -> int:
"""REST API エンドポイントから JSON を取得して CSV として書き込む。"""
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=30) as resp:
if resp.status != 200:
print(f"エラー: API がステータス {resp.status} を返しました")
return 0
body = resp.read().decode("utf-8")
except urllib.error.URLError as exc:
print(f"エラー: {url} に到達できません: {exc.reason}")
return 0
try:
records = json.loads(body)
except json.JSONDecodeError as exc:
print(f"エラー: API が不正な JSON を返しました: {exc.msg}")
return 0
if not isinstance(records, list) or not records:
print("エラー: API から空でない JSON 配列を期待しています")
return 0
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
return len(records)
rows = api_response_to_csv(
"https://api.internal.example.com/v2/deployments?status=completed",
"completed_deployments.csv",
)
print(f"{rows} 件のデプロイメントを CSV にエクスポートしました")urllib を使っています。requests がインストール済みなら、urllib の部分を resp = requests.get(url, timeout=30); records = resp.json() に置き換えてください — 残りの CSV 書き込みコードはそのままで動きます。コマンドラインでの JSON から CSV への変換
ターミナルで1行だけ必要なこともあります。Python の -c フラグを使えばスクリプトファイルを作らずにすばやく変換できます。より複雑な変換には、jq でデータを整形してからパイプします。
# Python ワンライナー: stdin から JSON を読み込み、stdout に CSV を書き込む cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " # 出力をファイルに保存 cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " > orders.csv
# json2csv.py として保存して実行: python3 json2csv.py input.json -o output.csv
python3 -c "
import json, csv, argparse, sys
parser = argparse.ArgumentParser(description='JSON 配列を CSV に変換する')
parser.add_argument('input', help='JSON ファイルのパス')
parser.add_argument('-o', '--output', default=None, help='出力 CSV パス(デフォルト: stdout)')
parser.add_argument('-d', '--delimiter', default=',', help='CSV デリミタ')
args = parser.parse_args()
with open(args.input) as f:
data = json.load(f)
out = open(args.output, 'w', newline='') if args.output else sys.stdout
writer = csv.DictWriter(out, fieldnames=data[0].keys(), delimiter=args.delimiter)
writer.writeheader()
writer.writerows(data)
if args.output:
out.close()
print(f'{len(data)} 行を {args.output} に書き込みました', file=sys.stderr)
" "$@"# csvkit のインストール: pip install csvkit
# jq でフィールドを選択・整形し、in2csv が CSV フォーマットを処理する
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv
# Miller (mlr) も JSON から CSV への別の選択肢
mlr --json2csv cat orders.json > orders.csvMiller(mlr)は JSON、CSV、TSV をファーストクラスのフォーマットとして扱うスタンドアロンバイナリで、 Python ランタイムが不要です。 --json2csv フラグは JSON 入力を1回のパスで CSV に変換し、出力を書き込む前に同じコマンドで Miller の verb を連結してフィルタリング、ソート、列名変更ができます。macOS では Homebrew(brew install miller) またはお使いの Linux パッケージマネージャーでインストールできます。 Python 環境を起動せずに高速な JSON から CSV への変換が必要な CI パイプラインで特に便利です。
高パフォーマンスの代替手段 — pyarrow を使った pandas
数千万行規模のデータセットには、 pyarrow バックエンドを使った pandas がデフォルトよりも大幅に高速に読み書きできます。 C バックエンドの Arrow エンジンは、Python の行ごとの csv モジュールよりも列指向データを 効率的に処理します。API は同じ — エンジンパラメータを設定するだけです。
pip install pyarrow
import pandas as pd
# pyarrow エンジンで JSON を読み込む(大きなファイルの高速パース)
df = pd.read_json("sensor_readings.json", engine="pyarrow")
# to_csv にはエンジンパラメータがないが、読み込みと書き込みの間の
# DataFrame 操作は pyarrow の列指向レイアウトから恩恵を受ける
df.to_csv("sensor_readings.csv", index=False)
# 本当に大きなエクスポートには、CSV の代わりに Parquet への書き込みを検討する
# — バイナリフォーマット、5〜10倍小さく、型を保持する
df.to_parquet("sensor_readings.parquet", engine="pyarrow")数百 MB 以上の JSON を処理していて、最終的なコンシューマーが Parquet を受け入れるなら、 CSV は完全にスキップしましょう。Parquet は小さく、列の型を保持し、Redshift と BigQuery の両方が ネイティブでロードできます。CSV は損失のあるフォーマット — すべての値が文字列になります。
ターミナルでのシンタックスハイライト付き出力
rich ライブラリはターミナルで罫線、整列、色付きのテーブルを描画します — 出力ファイルを開かずに開発中の変換をプレビューするのに便利です。
pip install rich
import json
from rich.console import Console
from rich.table import Table
json_string = """
[
{"hostname": "web-prod-1", "cpu_percent": 72.3, "memory_mb": 3840, "uptime_hours": 720},
{"hostname": "web-prod-2", "cpu_percent": 45.1, "memory_mb": 2560, "uptime_hours": 168},
{"hostname": "db-replica-1", "cpu_percent": 91.7, "memory_mb": 7680, "uptime_hours": 2160}
]
"""
records = json.loads(json_string)
console = Console()
table = Table(title="サーバーメトリクスのプレビュー", show_lines=True)
for key in records[0]:
table.add_column(key, style="cyan" if key == "hostname" else "white")
for row in records:
table.add_row(*[str(v) for v in row.values()])
console.print(table)
# ターミナルに罫線付きのカラーハイライトテーブルを描画するcsv.DictWriter または DataFrame.to_csv() を使い、rich はプレビューのみに使ってください。大きな JSON ファイルの処理
json.load() はファイル全体をメモリに読み込みます。200 MB の JSON ファイルでは、生テキストが約 200 MB、 さらに Python オブジェクトのオーバーヘッドが加わり、ヒープ使用量は 500 MB 以上になりやすいです。 100 MB を超えるファイルには、 ijson で入力をストリーミングし、行ごとに CSV を書き込みましょう。
pip install ijson
ijson を使って JSON 配列を CSV にストリーミングする
import ijson
import csv
def stream_json_to_csv(json_path: str, csv_path: str) -> int:
"""大きな JSON 配列をすべてメモリに読み込まずに CSV に変換する。"""
with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
# ijson.items はトップレベル配列の各要素を1つずつ yield する
records = ijson.items(jf, "item")
first_record = next(records)
fieldnames = list(first_record.keys())
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for record in records:
writer.writerow(record)
count += 1
return count
rows = stream_json_to_csv("clickstream_2026_03.json", "clickstream_2026_03.csv")
print(f"{rows} レコードを CSV にストリーミングしました")NDJSON / JSON Lines — 1行に1オブジェクト
NDJSON(Newline-Delimited JSON)、JSON Lines または .jsonl とも呼ばれるこのフォーマットは、ラッピングの配列なしに1行に1つの有効な JSON オブジェクトを格納します。 このフォーマットは、ログパイプライン、イベントストリーム(Kafka、Kinesis)、および Elasticsearch や BigQuery などのサービスからの一括エクスポートでよく使われます。 各行は自己完結した JSON オブジェクトなので、ファイルハンドルに対するプレーンな Python の for ループで NDJSON ファイルを処理できます — ijson ライブラリは不要です。 ソースデータがすでに JSON Lines フォーマットの場合、これが最もシンプルなストリーミングアプローチであり、 ファイルサイズに関係なくメモリを一定に保ちます。
import json
import csv
def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
"""改行区切り JSON ファイルを1行ずつ CSV に変換する。"""
with open(ndjson_path, encoding="utf-8") as nf:
first_line = nf.readline()
first_record = json.loads(first_line)
fieldnames = list(first_record.keys())
with open(csv_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for line in nf:
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
writer.writerow(record)
count += 1
except json.JSONDecodeError:
continue # 不正な行はスキップ
return count
rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"{rows} 件のログエントリを CSV に変換しました")json.load() で読み込むと、Python オブジェクトのオーバーヘッドにより 3〜5 GB の RAM を消費することがあります。ijson を使えば、ファイルサイズに関わらずメモリは一定に保たれます。 小さなファイルをさっと変換したいだけなら、 JSON to CSV コンバーター に貼り付ける方が手軽です。よくあるミス
問題: csv モジュールは \r\n の行末を書き込みます。newline='' なしでは Python のテキストモードが Windows でさらに \r を追加し、二重スペースの出力になります。
解決策: CSV 書き込み用にファイルを開く際は常に newline='' を渡してください。macOS/Linux では無害です。
with open("output.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Windows ではすべてのデータ行の間に空白行が入るwith open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# 全プラットフォームでクリーンな出力問題: index=False なしでは、pandas が自動インクリメントの行番号列(0, 1, 2, ...)を追加し、元の JSON には存在しなかったデータで CSV が汚染されます。
解決策: to_csv() に index=False を渡してください。インデックス列が本当に必要なら df.index.name = 'row_num' で明示的に名前を付けてください。
df = pd.read_json("events.json")
df.to_csv("events.csv")
# CSV に余分な名前のない列が入る: ,event_id,timestamp,...
# 先頭のカンマが多くの CSV パーサーを壊すdf = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# クリーンな CSV: event_id,timestamp,...問題: JSON オブジェクトのキーが異なる場合(一部のレコードにオプションフィールドがある場合)、最初のレコードのキーを fieldnames として使うと、後のレコードにしか現れない列が黙って削除されます。
解決策: DictWriter を作成する前に全レコードのユニークなキーを収集してください。
records = json.load(f) writer = csv.DictWriter(out, fieldnames=records[0].keys()) # records[2] にしか現れない "discount" フィールドが消える
records = json.load(f) all_keys = list(dict.fromkeys(k for r in records for k in r)) writer = csv.DictWriter(out, fieldnames=all_keys, restval="") # 全レコードのすべてのキーが列として含まれる
問題: csv.DictWriter はネストされた dict に str() を呼び出すため、"{'city': 'Portland'}"のような生の Python repr が列の値になります。
解決策: pd.json_normalize() またはカスタムのフラット化関数を使って、ネストされたオブジェクトを最初にフラット化してください。
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# meta 列の内容: {'source': 'web', 'region': 'us-west'}import pandas as pd
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
df = pd.json_normalize(records, sep="_")
df.to_csv("events.csv", index=False)
# 列: id, meta_source, meta_regioncsv.DictWriter vs pandas — 簡易比較
依存ゼロが必要で JSON がフラットであり、スクリプトが制限された環境(CI コンテナ、Lambda 関数、 組み込み Python)で動く場合は csv.DictWriter を使ってください。 JSON がネストされていて、エクスポート前にデータの変換やフィルタリングが必要な場合、または すでに pandas のワークフローにいる場合は pd.json_normalize() + to_csv() を使ってください。 メモリに収まらないファイルには、定数メモリのストリーミングのために ijson と csv.DictWriter を組み合わせてください。
コードなしのすばやい変換には、 ToolDeck の JSON to CSV コンバーター が Python のセットアップなしで対応します。
よくある質問
pandas を使わずに Python で JSON を CSV に変換するには?
組み込みの json モジュールと csv モジュールを使用します。json.load() で JSON ファイルをディクショナリのリストにパースし、最初のディクショナリのキーから fieldnames を取得して csv.DictWriter を作成し、writeheader() と writerows() を呼び出します。この方法は外部依存ゼロで、あらゆる Python 3.x 環境で動作します。また、DataFrame の割り当てオーバーヘッドがないため、小さなファイルでは pandas より高速です。JSON オブジェクトのキーがレコード間で一致しない場合は、dict.fromkeys(k for r in records for k in r) で全ユニークキーを収集してから fieldnames に渡すことで、列の欠落を防げます。
import json
import csv
with open("orders.json") as f:
records = json.load(f)
with open("orders.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)CSV に変換するとき、ネストされた JSON はどう処理しますか?
フラットな JSON 配列は直接 CSV の行にマップできますが、ネストされたオブジェクトは最初にフラット化する必要があります。pandas を使う場合、pd.json_normalize() が自動的に処理してくれます — ネストされたキーをドット区切りで結合します(例:「address.city」)。pandas なしの場合は、ディクショナリを再帰的に走査してキーをデリミタで連結する関数を書きます。複数レベルの深いネスト構造にも json_normalize は1回のパスで対応します。sep パラメータはキーセグメント間の結合文字を制御します — SQL インポートやスプレッドシートの数式の互換性を考えると、デフォルトのドットよりアンダースコアの方が安全です。
import pandas as pd
nested_data = [
{"id": "ord_91a3", "customer": {"name": "田中花子", "email": "tanaka.hanako@example.com"}},
]
df = pd.json_normalize(nested_data, sep="_")
# カラム: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)Windows でデータ行の間に空白行が入るのはなぜですか?
csv モジュールはデフォルトで \r\n の行末を書き込みます。Windows でテキストモードでファイルを開くと、さらに \r が追加されて \r\r\n になり、空白行として表示されます。解決策は open() に常に newline="" を渡すことです。これにより Python は行末の変換を行わず、csv モジュールに処理を委ねます。このパターンは OS に関係なく必要です — macOS と Linux では無害で、Windows では必須です。Python のドキュメントでも、CSV 書き込み用にファイルを開く正しい方法として csv モジュールのセクションで明示されています。
# 誤り — Windows で空白行が入る
with open("output.csv", "w") as f:
writer = csv.writer(f)
# 正しい — newline="" で二重の \r を防ぐ
with open("output.csv", "w", newline="") as f:
writer = csv.writer(f)既存の CSV ファイルに JSON レコードを追記するには?
ファイルを追記モード("a")で開き、同じ fieldnames で DictWriter を作成します。ヘッダー行はすでに存在するため writeheader() はスキップします。pandas では to_csv(mode="a", header=False) を使います。既存ファイルの列順と一致させてください。そうしないとデータが間違った列に入ります。既存ファイルの列順が不明な場合は、csv.DictReader でファイルを開き、その fieldnames 属性から列名を読み取ってから追記用のライターを作成してください。
import csv
new_records = [
{"order_id": "ord_f4c1", "total": 89.50, "status": "shipped"},
]
with open("orders.csv", "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["order_id", "total", "status"])
writer.writerows(new_records)Python で大きな JSON ファイルを最も速く CSV に変換するには?
500 MB 未満のファイルでは、pd.read_json() の後に to_csv() を呼ぶ方法が最速の単一呼び出しアプローチです — pandas は内部で最適化された C コードを使用しています。500 MB を超えるファイルには、ijson を使って JSON レコードをストリーミングし、csv.DictWriter で行ごとに CSV に書き込みます。これにより、ファイルサイズに関わらずメモリ使用量を一定に保てます。NDJSON ファイル(1行に1つの JSON オブジェクト)の場合は ijson すら不要です — ファイルハンドルに対する普通の Python for ループで各行を独立して処理でき、サードパーティライブラリなしで定数メモリを実現できます。
# メモリに収まるファイルに高速
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)
# メモリに収まらないファイルにストリーミング
import ijson, csv
with open("huge.json", "rb") as jf, open("huge.csv", "w", newline="") as cf:
records = ijson.items(jf, "item")
first = next(records)
writer = csv.DictWriter(cf, fieldnames=first.keys())
writer.writeheader()
writer.writerow(first)
for record in records:
writer.writerow(record)Python でファイルではなく stdout に CSV 出力できますか?
はい。csv.writer() または csv.DictWriter() のファイルオブジェクトとして sys.stdout を渡します。シェルスクリプトでの出力パイプやクイックデバッグに便利です。pandas では to_csv(sys.stdout, index=False) を呼ぶか、to_csv(None) で文字列として取得して print できます。一時ファイルは不要です。Windows の stdout に書き込む場合は、stdout がデフォルトでテキストモードで開くため、二重キャリッジリターン問題を避けるために最初に sys.stdout.reconfigure(newline="") を呼び出してください。
import csv
import sys
import json
data = json.loads('[{"host":"web-1","cpu":72.3},{"host":"web-2","cpu":45.1}]')
writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
# host,cpu
# web-1,72.3
# web-2,45.1関連ツール
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.
Priya is a data scientist and machine learning engineer who has worked across the full Python data stack — from raw data ingestion and cleaning to model deployment and monitoring. She is passionate about reproducible research, Jupyter-based workflows, and the practical engineering side of ML. She writes about NumPy, Pandas, data serialisation, and the Python patterns that make data pipelines reliable at scale.