- Python
Python JSON 読み書き|コピペレシピと実行結果・3.14対応
PythonでJSONを読み書きするには、標準ライブラリの json モジュールを使い、ファイルは json.load() / json.dump()、文字列は json.loads() / json.dumps() で変換します。 日本語を含むなら、open() に encoding="utf-8"、書き出しに ensure_ascii=False を付けるのが基本形です。
この記事では、基本の使い分けに加えて、「datetimeが保存できない」「JSONDecodeErrorの原因がわからない」「数値キーが文字列に変わった」など実務のつまずきをコピペで動くレシピにまとめました。コードは Python 3.14.7 で実行し、出力をそのまま掲載しています(3.13以前で違う箇所は明記)。
Python JSON 読み書きの早見表
| やりたいこと | 書き方 |
|---|---|
| ファイルを読む | with open(path, encoding="utf-8") as f: data = json.load(f) |
| ファイルに書く | json.dump(obj, f, ensure_ascii=False, indent=2)(open(path, "w", encoding="utf-8")) |
| 文字列 → Python | json.loads(text)(bytes も可) |
| Python → 文字列 | json.dumps(obj, ensure_ascii=False) |
| 日本語をそのまま出す | ensure_ascii=False |
| 整形/キー順を固定 | indent=2 / sort_keys=True |
| 空白なしで出す | separators=(",", ":") |
| datetime・Decimal を保存 | default=関数 または cls=JSONEncoder派生 |
| 小数を誤差なく読む | parse_float=Decimal |
| 読み込み時に型を戻す | object_hook=関数 |
| 重複キーを検出 | object_pairs_hook=関数 |
| NaN・Infinity を禁止 | allow_nan=False |
| 1行1件(JSON Lines) | 1行ずつ loads / dumps(obj) + "\n" |
| BOM付きファイルを読む | encoding="utf-8-sig" |
| 壊れた場所を知る | except json.JSONDecodeError as e: → e.lineno e.colno |
| ターミナルで整形 | python -m json file.json(3.14〜。以前は python -m json.tool) |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python JSON 読み書き|コピペレシピと実行結果・3.14対応」
load・loads・dump・dumps の使い分け
末尾に s が付くものは文字列(string)、付かないものはファイルを扱います。
| 関数 | 入力 → 出力 | よく使う場面 |
|---|---|---|
json.load(fp) |
ファイル → Pythonの値 | 設定・保存データを読む |
json.loads(s) |
文字列 → Pythonの値 | APIの応答本文を読む |
json.dump(obj, fp) |
Pythonの値 → ファイル | データを保存する |
json.dumps(obj) |
Pythonの値 → 文字列 | APIに送る、ログに出す |
型は dict⇔object、list・tuple⇔array、str⇔string、int・float⇔number、True/False⇔true/false、None⇔null に対応します。tuple は list になり、キーはすべて文字列になるため、読み戻すと元と同じにならない場合があります(レシピ4)。
1. JSONファイルを読み書きする(基本形)
open() には encoding="utf-8" を必ず付けます。 省略するとOSの既定の文字コードが使われ、Windowsでは日本語を含むファイルで UnicodeDecodeError になることがあります。
import json
orders = [{"order_id": "A-001", "item": "ボールペン", "qty": 3}]
with open("orders.json", "w", encoding="utf-8") as f:
json.dump(orders, f, ensure_ascii=False, indent=2)
with open("orders.json", encoding="utf-8") as f:
data = json.load(f)
print(type(data), data[0]["item"])
print(open("orders.json", encoding="utf-8").read())
実行結果:
<class 'list'> ボールペン
[
{
"order_id": "A-001",
"item": "ボールペン",
"qty": 3
}
]
2. 日本語をそのまま出す・整形する
json.dumps() は既定で日本語を「バックスラッシュ+u+16進4桁」の英数字に置き換えます。 JSONとしては正しいものの人が読めないため、ensure_ascii=False を付けます。
import json
d = {"qty": 3, "item": "ペン", "tags": ["赤", "黒"]}
s = json.dumps(d) # 既定
print("ペン" in s, s.isascii(), json.loads(s) == d)
print(json.dumps(d, ensure_ascii=False)) # 日本語そのまま
print(json.dumps(d, ensure_ascii=False, sort_keys=True))
print(json.dumps(d, ensure_ascii=False, separators=(",", ":")))
print(json.dumps(d, ensure_ascii=False, indent=2))
実行結果:
False True True
{"qty": 3, "item": "ペン", "tags": ["赤", "黒"]}
{"item": "ペン", "qty": 3, "tags": ["赤", "黒"]}
{"qty":3,"item":"ペン","tags":["赤","黒"]}
{
"qty": 3,
"item": "ペン",
"tags": [
"赤",
"黒"
]
}
1行目の True(3つ目)のとおり、どちらで書いても読み戻した値は同じです。違うのは見た目だけなので、目的で選びます。
| 目的 | 設定 |
|---|---|
| 人が読む設定ファイル | indent=2, ensure_ascii=False |
| Gitで差分を見る | 上に加えて sort_keys=True |
| 通信量を減らす | separators=(",", ":") |
| ASCIIしか通らない所へ渡す | 既定(ensure_ascii=True) |
キーの順番は、既定では dict に入れた順のままです。
3. datetime を保存しようとして出るエラー(3.14で場所がわかる)
datetime・Decimal・set などは、そのままでは json.dumps() できません。
import json
from datetime import datetime
order = {"order_id": "A-001", "items": [{"name": "ペン", "added": datetime(2026, 9, 28, 10, 30)}]}
json.dumps(order, ensure_ascii=False)
Python 3.14 の実行結果(末尾):
TypeError: Object of type datetime is not JSON serializable
when serializing dict item 'added'
when serializing list item 0
when serializing dict item 'items'
Python 3.11 では最初の1行だけです。3.14 ではエラーに「どこの値か」の注記が付き、下から items → 0番目 → added とたどれます。直し方はレシピ5です。
4. キーが数値 → 文字列になる罠
JSONのobjectのキーは必ず文字列です。
import json
print(json.dumps({"t": (1, 2), 1: "a", False: "b", None: "c"}))
d = {1: "one", 10: "ten", 2: "two"}
back = json.loads(json.dumps(d, sort_keys=True))
print(back, back == d, back.get(1), back.get("1"))
try:
json.dumps({(1, 2): "x"})
except TypeError as e:
print("TypeError:", e)
実行結果:
{"t": [1, 2], "1": "a", "false": "b", "null": "c"}
{'1': 'one', '2': 'two', '10': 'ten'} False None one
TypeError: keys must be str, int, float, bool or None, not tuple
- 読み戻すと
back.get(1)がNoneになります。back["1"]で引くか、{int(k): v for k, v in back.items()}で戻します。 sort_keys=Trueは文字列化の前の値で並べるため、1, 2, 10の数値順になります。- tuple のキーはエラーです(
skipkeys=Trueはそのキーを捨てて続行します)。
5. datetime・Decimal・set・dataclass・Enum を変換する
default= に「変換できない値を受け取り、JSONにできる値を返す関数」を渡します。
import dataclasses
import json
from datetime import date, datetime, timezone
from decimal import Decimal
from enum import Enum
class Status(Enum):
NEW = "new"
@dataclasses.dataclass
class Item:
name: str
price: Decimal
def to_json(o):
if isinstance(o, (datetime, date)):
return o.isoformat()
if isinstance(o, Decimal):
return str(o) # 精度を保つため文字列に
if isinstance(o, (set, frozenset)):
return sorted(o) # 順序を安定させる
if isinstance(o, Enum):
return o.value
if dataclasses.is_dataclass(o) and not isinstance(o, type):
return dataclasses.asdict(o)
raise TypeError(f"{type(o).__name__} は JSON にできません")
order = {
"ordered_at": datetime(2026, 9, 28, 10, 30, tzinfo=timezone.utc),
"deliver_on": date(2026, 10, 1),
"status": Status.NEW,
"tags": {"急ぎ", "ギフト"},
"items": [Item("ペン", Decimal("120.50"))],
}
print(json.dumps(order, default=to_json, ensure_ascii=False))
実行結果:
{"ordered_at": "2026-09-28T10:30:00+00:00", "deliver_on": "2026-10-01", "status": "new", "tags": ["ギフト", "急ぎ"], "items": [{"name": "ペン", "price": "120.50"}]}
| 型 | おすすめの変換 | 理由 |
|---|---|---|
datetime / date |
isoformat() |
fromisoformat() で戻せる。タイムゾーンも残る |
Decimal |
str() |
float() にすると誤差が入る |
set |
sorted() |
出力順が毎回同じになる |
Enum |
.value |
IntEnum / StrEnum は変換なしで出力される |
| dataclass | dataclasses.asdict() |
入れ子も dict になる |
Path / UUID |
str() |
最後の raise TypeError は残します。 何でも str(o) にすると、想定外の値が黙って文字列化されます。
複数箇所で使うなら JSONEncoder を継承したクラスにして cls= で渡します。
import json
from datetime import datetime
from decimal import Decimal
class AppEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, Decimal):
return str(o)
return super().default(o) # 対応外は TypeError
print(json.dumps({"at": datetime(2026, 9, 28, 9, 0), "total": Decimal("1980.00")}, cls=AppEncoder))
実行結果:
{"at": "2026-09-28T09:00:00", "total": "1980.00"}
6. 読み込み時に型を戻す(parse_float・object_hook・object_pairs_hook)
import json
from datetime import datetime
from decimal import Decimal
text = '{"total": 0.1, "ordered_at": "2026-09-28T10:30:00+09:00", "id": 1, "id": 2}'
print(json.loads(text)) # 重複キーは後勝ち
print(json.loads(text, parse_float=Decimal)["total"] * 3)
def restore(obj): # 日時文字列を戻す
for k, v in obj.items():
if k.endswith("_at") and isinstance(v, str):
obj[k] = datetime.fromisoformat(v)
return obj
print(json.loads(text, object_hook=restore)["ordered_at"])
def no_dup(pairs): # 重複キーをエラーに
keys = [k for k, _ in pairs]
if len(keys) != len(set(keys)):
raise ValueError(f"重複キーがあります: {keys}")
return dict(pairs)
try:
json.loads(text, object_pairs_hook=no_dup)
except ValueError as e:
print(e)
実行結果:
{'total': 0.1, 'ordered_at': '2026-09-28T10:30:00+09:00', 'id': 2}
0.3
2026-09-28 10:30:00+09:00
重複キーがあります: ['total', 'ordered_at', 'id', 'id']
parse_float=Decimalで0.1 * 3が0.3のまま計算できます(float なら0.30000000000000004)。object_hookはすべての object ごとに呼ばれるため、入れ子の中の日時も戻ります。- 重複キーは標準では後の値で黙って上書きされます。外部データを厳密にチェックするなら
object_pairs_hookを使います。
7. JSONDecodeError のメッセージ別の原因と直し方
json.JSONDecodeError は ValueError のサブクラスで、msg・lineno(行)・colno(列)・pos(文字位置)を持っています。
import json
cases = {
"シングルクォート": "{'id': 1}",
"末尾のカンマ": '{"id": 1, "qty": 2,}',
"True(Python表記)": '{"active": True}',
"空文字": "",
"コメント": '{"id": 1 // メモ\n}',
"2つのJSONが連続": '{"id": 1}\n{"id": 2}',
}
for label, text in cases.items():
try:
json.loads(text)
except json.JSONDecodeError as e:
print(f"{label}: {e.msg} | 行{e.lineno} 列{e.colno}")
実行結果:
シングルクォート: Expecting property name enclosed in double quotes | 行1 列2
末尾のカンマ: Illegal trailing comma before end of object | 行1 列19
True(Python表記): Expecting value | 行1 列12
空文字: Expecting value | 行1 列1
コメント: Expecting ',' delimiter | 行1 列10
2つのJSONが連続: Extra data | 行2 列1
末尾のカンマは 3.13 から専用のメッセージになりました。3.12.3 では Expecting property name enclosed in double quotes(列20)と表示されます。
| メッセージ | よくある原因 | 直し方 |
|---|---|---|
Expecting property name enclosed in double quotes |
' で囲んだ、str(dict) の結果を渡した |
作る側で json.dumps() を使う |
Illegal trailing comma before end of object |
最後の要素の後ろに , |
カンマを消す |
Expecting value(列1) |
空ファイル、HTMLのエラーページを受け取った | repr() で中身を確認。APIはステータスコードを先に確認 |
Expecting value(途中) |
True None などPythonの書き方 |
true null にする |
Expecting ',' delimiter |
コメント、カンマ抜け | JSONはコメント不可 |
Extra data |
JSON Lines を json.load() で読んだ |
1行ずつ読む(レシピ8) |
Unexpected UTF-8 BOM (decode using utf-8-sig) |
BOM付きUTF-8のファイル | encoding="utf-8-sig" で開く |
壊れた場所を見やすく表示する読み込み関数です。utf-8-sig はBOMがなくても読めるので、出どころがわからないファイルに向きます。
import json
from pathlib import Path
Path("broken.json").write_text('{\n "id": 1,\n "qty": 3,\n}\n', encoding="utf-8")
def load_json(path):
text = Path(path).read_text(encoding="utf-8-sig") # BOM付きも読める
try:
return json.loads(text)
except json.JSONDecodeError as e:
line = text.splitlines()[e.lineno - 1]
print(f"{path}:{e.lineno}:{e.colno}: {e.msg}\n {line}\n {' ' * (e.colno - 1)}^")
raise
try:
load_json("broken.json")
except json.JSONDecodeError:
pass
実行結果:
broken.json:3:11: Illegal trailing comma before end of object
"qty": 3,
^
8. JSON Lines(.jsonl)を読み書きする
JSON Lines は「1行に1つのJSON」を並べた形式です。 追記が1行で済み、1行ずつ読めるため、ログや大量データに向きます。
import json
with open("events.jsonl", "w", encoding="utf-8") as f:
for e in [{"type": "order", "qty": 3}, {"type": "cancel"}, {"type": "order", "qty": 1}]:
f.write(json.dumps(e, ensure_ascii=False) + "\n") # indentは付けない
total = 0
with open("events.jsonl", encoding="utf-8") as f:
for lineno, line in enumerate(f, 1):
if line.strip():
rec = json.loads(line)
total += rec.get("qty", 0)
print("合計:", total)
実行結果:
合計: 4
indent を付けると1件が複数行になり、JSON Lines として読めなくなります。 追記は "a" モードで同じように1行書くだけです。
9. NaN・Infinity の扱い(allow_nan)
JSONの仕様(RFC 8259)に NaN と Infinity はありませんが、Pythonの json は既定で出力も読み込みもします。
import json
data = {"avg": float("nan"), "max": float("inf")}
print(json.dumps(data))
try:
json.dumps(data, allow_nan=False)
except ValueError as e:
print("ValueError:", e)
print(json.loads('{"v": NaN}'))
実行結果:
{"avg": NaN, "max": Infinity}
ValueError: Out of range float values are not JSON compliant: nan
{'v': nan}
他の言語のパーサーでは読めないことがあるため、外部に渡すデータは allow_nan=False にし、事前に None(null)へ置き換えると安全です。読み込み側で拒否するには parse_constant= に例外を出す関数を渡します。
10. 大きいJSONファイルを読む(ijson の紹介)
json.load() はファイル全体を一度にメモリへ載せます。 巨大なファイルは、元データを JSON Lines にするか、少しずつ読める外部ライブラリ ijson を使います。
import ijson
with open("big.json", "rb") as f:
for rec in ijson.items(f, "orders.item"): # orders 配列の各要素
...
約5MB・20万件の配列で tracemalloc のピークを比べたところ、json.load() は 49.2MB、ijson.items() は 1.0MB でした(ijson 3.5.1)。処理時間は標準の json が速いことが多いので、メモリに収まるなら json.load() で十分です。
11. ターミナルで整形・検証する(python -m json)
Python 3.14 から python -m json でJSONを整形・検証できます(python -m json.tool も引き続き使えます)。3.14 では出力が色付きになり、環境変数 NO_COLOR や PYTHON_COLORS で制御できます。
echo '{"item":"ペン","qty":3}' > item.json
python3.14 -m json --no-ensure-ascii --indent 2 item.json
python3.14 -m json --compact --no-ensure-ascii item.json
echo '{"a":1,}' | python3.14 -m json; echo "exit=$?"
実行結果(NO_COLOR=1 で実行):
{
"item": "ペン",
"qty": 3
}
{"item":"ペン","qty":3}
Illegal trailing comma before end of object: line 1 column 7 (char 6)
exit=1
壊れたJSONは終了コード1になるため、CIの構文チェックにも使えます。Python 3.11 で python3.11 -m json とすると No module named json.__main__; 'json' is a package and cannot be directly executed になるので、json.tool を使います。
| オプション | 意味 |
|---|---|
--sort-keys |
キーを並べ替える |
--no-ensure-ascii |
日本語をそのまま出す |
--indent N / --tab |
インデント幅/タブ |
--no-indent / --compact |
1行(空白あり/なし) |
--json-lines |
1行ずつ別のJSONとして読む |
12. json・orjson・pydantic の選び方
| 観点 | json(標準) |
orjson |
pydantic |
|---|---|---|---|
| 導入 | 不要 | uv add orjson |
uv add pydantic |
dumps の戻り値 |
str |
bytes |
―(model_dump_json() は str) |
| datetime・dataclass | default= が必要 |
そのまま出力できる | 型定義どおり変換 |
| 型の検証 | なし | なし | あり |
| 向いている場面 | 設定・小〜中規模データ | 大量のJSONを速く変換 | APIの入出力、外部データの検証 |
orjson 3.12.0 では orjson.dumps({"item": "ペン", "at": datetime(2026, 9, 28, 10, 30)}) が default= なしで bytes を返し、.decode() すると {"item":"ペン","at":"2026-09-28T10:30:00"} でした。pydantic 2.13.5 では "qty": "3" を int 型のフィールドに渡すと 3 に変換され、"たくさん" なら 1 validation error for Order で止まりました。まず標準の json、速度が必要なら orjson、検証が必要なら pydantic と段階的に選ぶとよいでしょう。
よくある質問
Q. print(dict) の出力をJSONとして読むとエラーになるのはなぜですか?
A. print や str() はPythonの表記(シングルクォート、True、None)で出力するため、JSONではありません。JSONが必要なら必ず json.dumps() を使います。
Q. datetime を保存して、読み込むときに datetime に戻せますか?
A. 書き出しは default= で isoformat() にし、読み込みは object_hook= で datetime.fromisoformat() を使えば戻せます(レシピ5・6)。JSONに日時の型はないため、どのキーが日時かのルールを決めておくのがポイントです。
Q. JSONにコメントを書けますか?
A. 標準のJSONには書けません。人が編集する設定ファイルなら、コメントが書けて標準ライブラリの tomllib(3.11〜)で読める TOML も選択肢です。
まとめ
- ファイルは
load/dump、文字列はloads/dumps。open()にはencoding="utf-8" - 日本語をそのまま出すなら
ensure_ascii=False、差分を見るならindent=2, sort_keys=True - datetime・Decimal・set・dataclass は
default=で変換し、対応外はTypeErrorのまま残す - 読み込み側は
parse_float=Decimal・object_hook・object_pairs_hookで型と品質を整える JSONDecodeErrorのlineno/colnoで壊れた場所を特定し、メッセージ別の表で直す- 数値キーは文字列になる、NaN は既定で出力されるの2点は読み戻しで確認する
- 3.14 は
python -m jsonと、位置がわかる TypeError が加わった
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python JSON 読み書き|コピペレシピと実行結果・3.14対応」
株式会社RJCでは、データの受け渡しでつまずきやすいポイントを、実際に動かして確かめながらチームで共有しています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。