Pythonで JSON を扱う:ファイルの読み書き、CSV、pathlib、UTF-8
Pythonでは、標準ライブラリの json モジュールだけでJSONを扱えます。json.load はファイルから読み込み、json.dump はファイルに書き込みます。json.loads と json.dumps は文字列を扱います。ファイルは with open(path, encoding="utf-8") で開き、パスは pathlib.Path で組み立て、表形式のデータは csv モジュールで読みます。
Pythonでのデータ形式JSONの扱いには、外部ライブラリは要りません。json モジュールは標準ライブラリに含まれていて、辞書やリストを文字列へ、また文字列から辞書やリストへ変換できます。JSONと組み合わせて、ほぼ必ず必要になるのが、with 文を使う open 関数、パスを扱う pathlib モジュール、表データを扱う csv モジュールの3つです。ここでは、動くコードと、つまずきやすいポイントを順に見ていきます。
Pythonのopen関数:モードと文字コード
テキストファイルは、組み込みの open(path, mode, encoding=...) で開きます。戻り値のファイルオブジェクトには read、readline、write メソッドがあり、for文で1行ずつ回せます。
| モード | 動作 |
|---|---|
"r" | 読み込み(既定値)。ファイルがなければ FileNotFoundError |
"w" | 書き込み。ファイルを作成するか、開いた時点で中身を空にする |
"a" | 末尾に追記。既存の内容は残す |
"x" | 新規作成。すでにあれば FileExistsError |
"b" | バイナリとして扱う("rb"、"wb") |
なぜwithを使うのか
ファイルは必ず閉じる必要があります。閉じないとデータがディスクに書かれないことがあり、また、システム側でファイルのハンドルを握ったままになります。with 文を使えば、ブロックの中でエラーが起きても、自動的にファイルが閉じられます。
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("1行目\n")
f.write("2行目\n")
with open("notes.txt", encoding="utf-8") as f:
for line in f:
print(line.rstrip("\n"))
for文でファイルを回すと、1行ずつ読み込まれるため、ファイル全体がメモリに載ることはありません。ギガバイト単位のログでも扱えます。f.read() は全体を1つの文字列で返し、f.readlines() は行のリストを返します。
文字列をファイルに書いても、既存の内容を消さないために
初心者が最もよくやるミスは、1行追加したいだけなのに "w" モードで開くことです。"w" は、最初の write より前、開いた時点で内容を消します。末尾に追加するには "a" を使います。すでにあるファイルを誤って上書きしたくないなら、"x" が安全です。
with open("log.txt", "a", encoding="utf-8") as f:
f.write("新しい記録\n")
write は改行を自動で入れないため、"\n" を明示的に書く必要があります。print(..., file=f) を使えば、改行を自動で付けてくれます。
UTF-8の文字化けはなぜ起きるのか
Python 3.15 より前では、open() に encoding を指定しなければ、システムの既定の文字コードが使われました。LinuxとmacOSではUTF-8ですが、日本語のWindowsでは cp932(Shift_JIS系)です。Linuxサーバーで問題なく動いていたスクリプトが、Windowsのパソコンでは文字化けしたり、UnicodeDecodeError で止まったりしていました。
Python 3.15 では、PEP 686 によってUTF-8モードがデフォルトになりました。encoding を指定しない open() は、どのOSでもUTF-8で読み書きします。古い動作に戻すには、環境変数 PYTHONUTF8=0 を設定するか、-X utf8=0 を指定します。システムのエンコーディングを明示的に使うなら encoding="locale" と書きます。
import sys
print(sys.flags.utf8_mode) # Python 3.15 では 1
open("legacy.txt", encoding="locale") # システムの既定の文字コード
open("old_excel.csv", encoding="cp932") # cp932 で保存されたファイル
注意点もあります。Excelやスクリプトで cp932 として保存された古いCSVは、UTF-8として読まれ、UnicodeDecodeError になります。その場合は、encoding="cp932" で読んでからUTF-8で保存し直します。どの版のPythonでも同じように動かすには、encoding を明示する習慣をつけてください。
pathlib:パスをオブジェクトとして扱う
パスを文字列で連結する("data" + "/" + name)のは扱いにくく、OSによって壊れることもあります。pathlib モジュールの Path クラスでは、/ 演算子でパスを結合でき、便利なプロパティも使えます。
from pathlib import Path
data = Path("data")
csv_path = data / "expenses.csv"
print(csv_path.name) # expenses.csv
print(csv_path.stem) # expenses
print(csv_path.suffix) # .csv
print(csv_path.parent) # data
print(csv_path.exists())
data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("こんにちは", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))
for p in data.glob("*.csv"):
print(p)
read_text と write_text は、小さなファイルなら open と with の組み合わせの代わりになります。Path オブジェクトは、通常の open にも、json や csv にも渡せます。スクリプトの置かれた場所を基準にしたパスは、Path(__file__).parent / "data" で作れます。
Python 3.14では、Path に copy() と move() メソッドが加わりました。単純なコピーであれば、もう shutil は必要ありません。同じバージョンでは、Zstandard圧縮のための標準モジュール compression.zstd も追加されました(PEP 784)。
Pythonのjson:load、dump、loads、dumps
JSONはテキストのデータ交換形式です。APIの応答や、設定ファイル、レポートの保存に使われます。json モジュールは、2つの操作を提供します。シリアライズ(Pythonオブジェクトからテキストへ)と、デシリアライズ(テキストからオブジェクトへ)です。関数は4つあり、混同しやすいので表にまとめます。
| 関数 | 動作 |
|---|---|
json.dumps(obj) | オブジェクトをJSON文字列にする |
json.loads(s) | JSON文字列をオブジェクトにする |
json.dump(obj, f) | オブジェクトを開いたファイルに書く |
json.load(f) | 開いたファイルからJSONを読む |
覚え方は簡単です。末尾の s は string(文字列)の意味で、文字列を扱うことを表します。s がなければ、ファイルを扱います。
型の対応
| Python | JSON |
|---|---|
dict | オブジェクト {} |
list、tuple | 配列 [] |
str | 文字列 |
int、float | 数値 |
True / False | true / false |
None | null |
タプルは読み戻すとリストになります。また、集合、日付、Decimal はjsonモジュールが直接扱えず、TypeError: Object of type ... is not JSON serializable になります。
JSONをファイルに保存する
import json
from pathlib import Path
report = {
"month": "2026-10",
"total": 15230.5,
"categories": {"食費": 8200, "交通費": 2100},
"tags": ["家", "仕事"],
}
path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
ensure_ascii=False を指定すると、日本語がそのまま保存されます。指定しないと、「食費」は "食費" のような形で書かれます。これは正しいJSONですが、人間には読みにくくなります。indent=2 は、2つのスペースによる字下げで整形します。
JSONをファイルから読み込む
with open("data/report.json", encoding="utf-8") as f:
data = json.load(f)
print(data["total"]) # 15230.5
print(data["categories"]["食費"]) # 8200
結果は普通の辞書やリストなので、いつもどおり扱えます。ファイルがJSONとして正しくないか、途中で切れている場合は、json.JSONDecodeError が、行番号と位置の情報とともに発生します。外部から届くデータを扱うときは、このエラーを捕まえるようにしてください。
JSON文字列の解析と見やすい出力
import json
raw = '{"name": "花子", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0]) # python
print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))
sort_keys=True は、出力時にキーを並べ替えます。gitでファイルの差分を比べるときに便利です。ターミナルからJSONファイルを整形するには、標準の方法があります。python -m json.tool data/report.json です。
HTTP APIの応答も、JSON文字列です。requests ライブラリの response.json() は、内部で json.loads を使っています。
注意:JSONのキーは常に文字列
d = {1: "1月", 2: "2月"}
back = json.loads(json.dumps(d))
print(back == d) # False
print(back) # {'1': '1月', '2': '2月'}
# back[1] -> KeyError。back["1"] と書く必要がある
fixed = {int(k): v for k, v in back.items()}
JSONのオブジェクトのキーは文字列だけです。そのため、数値のキーは変換の往復で文字列になります。読み込んだ後で、元の型に戻してください。
独自の型:日付とDecimal
import json
from datetime import date
def default(obj):
if isinstance(obj, date):
return obj.isoformat()
raise TypeError(f"{type(obj).__name__} はシリアライズできません")
print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}
default 引数に渡した関数は、扱えないオブジェクトごとに呼ばれます。より複雑な場合は、json.JSONEncoder を継承して default メソッドを上書きできます。
JSON配列に要素を追加する
JSONは、テキストのログのように末尾へ追記できません。ファイルを読み込み、変更して、全体を書き直す必要があります。
path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "新規"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")
レコードが多く、継続的に追加される場合は、JSON Lines という形式が向いています。1行に1つのJSONオブジェクトを書き、ファイルを "a" モードで開きます。
Pythonのcsvモジュール
CSVは、テキストファイルの中の表です。行がレコードで、値はカンマで区切られます。csv モジュールは、引用符、値の中のカンマ、改行を正しく扱います。手作業の split(",") では、これができません。
import csv
with open("data/expenses.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
total = 0.0
for row in reader:
total += float(row["amount"])
print(row["date"], row["category"], row["amount"])
print("合計:", total)
csv.DictReader は、ファイルの見出し行をキーにして、各行を辞書に変換します。値はすべて文字列です。数値や日付は自分で変換してください(float、int、date.fromisoformat)。区切り文字がセミコロン「;」のファイル(日本語版Excelが保存する形式のもの)は、delimiter=";" を指定して読みます。
rows = [
{"date": "2026-10-01", "category": "食費", "amount": 450},
{"date": "2026-10-02", "category": "交通費", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
writer.writeheader()
writer.writerows(rows)
書き込みのときは newline="" が必要です。これがないと、Windowsでは行の間に空行が入ります。ファイルをExcelで開く予定があるなら、encoding="utf-8-sig" を使ってください。先頭のBOMにより、ExcelがUTF-8として認識しやすくなります。
CSVからJSONへ:完全な例
import csv
import json
from collections import defaultdict
from pathlib import Path
src = Path("data/expenses.csv")
dst = Path("data/report.json")
by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
by_category[row["category"]] += float(row["amount"])
report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))
これは、レポートを作るスクリプトの基本形です。CSVを入力として読み、辞書で集計し、JSONとして出力します。
よくあるミス
FileNotFoundError― 相対パスは、スクリプトの場所ではなく、起動したフォルダを基準に解釈されます。Path(__file__).parentを使いましょう。- ファイルの中身が消えた ―
"a"ではなく"w"で開いてしまった。 UnicodeDecodeError― 別の文字コードのファイルです。cp932やutf-8-sigを明示してください。TypeError: Object of type set is not JSON serializable― リストに変換するか、default引数を渡します。- JSONを読み込んだ後の
KeyError― 数値のキーが文字列になっています。 - 自作のファイルを
json.pyやcsv.pyと名付けた ― 標準モジュールより優先されてしまい、import jsonで自分のファイルが読み込まれます。
まとめ
JSONをPythonで扱うのに、標準ライブラリで十分です。ファイルには json.load と json.dump、文字列には loads と dumps を使います。読みやすい出力には ensure_ascii=False と indent を指定します。ファイルは with で、文字コードを明示して開き、パスは pathlib で組み立て、表は csv.DictReader で読みます。講座では、このテーマを実践で扱います。家計簿ツールがCSVから支出を読み込み、レポートをJSONに保存します。
よくある質問
json.load と json.loads の違いは何ですか?
json.load は開いたファイルオブジェクトからJSONを読み込み、json.loads は文字列を解析します。同様に json.dump はファイルへ書き込み、json.dumps は文字列を返します。
日本語をJSONに \u で始まるエスケープなしで保存するには?
json.dump または json.dumps に ensure_ascii=False を渡し、ファイルを encoding="utf-8" で開きます。
JSONを扱うために、ライブラリをインストールする必要はありますか?
いいえ。json モジュールは標準ライブラリに含まれているので、import json と書くだけで使えます。
JSONに日付や集合を保存できますか?
直接はできません。日付は isoformat で文字列にし、集合はリストに変換するか、default 引数に関数を渡して処理します。
メモリに入らないほど大きなJSONはどう読めばよいですか?
標準の json.load はファイル全体を読みます。大量のデータは JSON Lines 形式で保存し、1行ずつ json.loads で解析するのがよい方法です。