「json」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「json」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「json の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データのやり取りに使う共通の形式です。
データを軽くして速く送るために使います。
スマホアプリの通信などで使われています。
ここではJSONを扱う時の注意点を読みます。
構造化データの軽量交換フォーマット
ensure_ascii=False。| ライブラリ | 速度 | datetime対応 | NaN対応 | 推奨用途 |
|---|---|---|---|---|
| json (標準) | 普通 | × | ○(設定で) | 教育・汎用 |
| orjson | 超高速 | ○ネイティブ | ×(オプション) | 本番API |
| ujson | 高速 | × | × | レガシー |
| simplejson | 普通 | × | ○ | Decimal対応 |
| rapidjson | 高速 | ○ | ○ | スキーマ検証 |
| ijson | 遅いがメモリ少 | × | × | 巨大JSON処理 |
学習用は json (標準)、 本番なら orjson、 大規模ストリーミングは ijson、 と覚えるのが楽。
エスケープ。 例: "text":"1行目
2行目"。{"format":"csv","content":"name,age\nAlice,30"}。 多重エスケープに注意。このチェックリストを通すだけで、 JSON 関連のバグの 9 割は防げます。
JSON は JavaScript 由来ですが、 各言語でほぼ同じ概念に対応します: Python の dict/list/str/int/float/bool/None、 Ruby の Hash/Array/...、 Java の Map/List/String/Integer/...、 Go の map/slice/string/int/float64/bool/nil。 この「共通基盤」が JSON 普及の核心です。
SSDSE-B-2026 のような統計データを JSON 化することで、 Python・R・JavaScript・Java など、 どの言語からも同じデータを扱えるようになります。 これが「言語非依存のデータ交換フォーマット」としての JSON の価値。 CSV にはない型情報があり、 XML にはない簡潔さがあります。
JSON を「言語横断データ表現」と捉えると、 データサイエンスの世界における位置づけが明確になります。 統計データはまず CSV で配布されますが、 アプリ統合・API 化・分析パイプライン化のいずれの段階でも JSON は必ず登場します。 SSDSE-B を「CSV→JSON→DB→API」の流れで扱えるようになれば、 公的統計を中心とした実用データサイエンスの全体像が掴めます。
本セクションでは「47 都道府県 × 12 年 × 約 110 変数」 を全て含む大きな JSON を生成する完全例を示します。 これがあれば、 別言語からも SSDSE-B のデータを呼び出せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import json import os import pandas as pd # 1. CSV 読み込み (2 行目の日本語見出しを列名にする) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) print(f'読み込み完了: {df.shape}') # 2. metadata セクション metadata = { 'name': 'SSDSE-B-2026', 'description': '都道府県別社会・人口統計データ (Standardized Statistical Data Set for Education Series-B)', 'source': '独立行政法人 統計センター', 'license': 'CC BY 4.0', 'years': sorted(df['年度'].unique().tolist()), 'prefectures': df['都道府県'].unique().tolist(), # 地域コード順 (北海道→沖縄県) 'variables': df.columns.tolist(), 'total_records': len(df), 'generated_at': pd.Timestamp.now().isoformat(), } # 3. data セクション (年度 × 都道府県のネスト) data_by_year = {} for year in metadata['years']: sub = df[df['年度'] == year] data_by_year[str(year)] = sub.set_index('都道府県').drop(columns=['年度']).to_dict(orient='index') out = {'metadata': metadata, 'data': data_by_year} os.makedirs('outputs', exist_ok=True) # 4. ファイル書き出し (整形あり) with open('outputs/ssdse_b_full.json', 'w', encoding='utf-8') as f: json.dump(out, f, ensure_ascii=False, indent=2) # 5. ファイル書き出し (ミニファイ、 本番用) with open('outputs/ssdse_b_full.min.json', 'w', encoding='utf-8') as f: json.dump(out, f, ensure_ascii=False, separators=(',', ':')) # 6. NDJSON 書き出し (1 行 = 1 県 × 1 年度) with open('outputs/ssdse_b.ndjson', 'w', encoding='utf-8') as f: for _, row in df.iterrows(): f.write(json.dumps(row.to_dict(), ensure_ascii=False, default=str) + '\n') print(f"年度: {metadata['years'][0]}〜{metadata['years'][-1]} ({len(metadata['years'])} 年度)、 都道府県: {len(metadata['prefectures'])}、 変数: {len(metadata['variables'])}") for path in ['outputs/ssdse_b_full.json', 'outputs/ssdse_b_full.min.json', 'outputs/ssdse_b.ndjson']: print(f'{path:32s} {os.path.getsize(path) / 1024:8.1f} KB') |
💬 564 行 × 112 列を 12 年度 × 47 都道府県に入れ子にして 3 形式で書き出した。indent=2 の整形版は 2740.8 KB、区切りの空白も削ったミニファイ版は 2125.3 KB で、中身は同じまま約 22% 小さくなる。NDJSON は 564 行 (1 行 = 1 県 × 1 年度) で 2259.8 KB と、ミニファイ版より大きい。各行に「総人口」などの 112 個のキー名を毎回書くうえ、json.dumps の既定の区切り ', ' と ': ' に空白が入るためで、その代わり 1 行ずつ読めばメモリに全体を載せずに処理できる。
これで SSDSE-B の全データが 3 形式の JSON として保存され、 各種ツール (JavaScript, Java, Go, R, Julia など) から再利用できる状態になります。
JSON は表面的には「{} と [] と : と , と "" だけのテキスト」ですが、 その背後には「言語横断の共通モデル」という哲学があります。 Python の dict、 JavaScript の Object、 Java の Map、 Go の map ─ 全ての主要言語が同じ概念を持つことで、 JSON はそれらをつなぐ橋として機能します。
SSDSE-B-2026 を CSV で読み、 JSON に変換し、 NDJSON でストリーミングし、 NoSQL に投入し、 API として配信し、 Schema で検証する ─ この一連の流れを自由に行えるようになれば、 公的統計を中心とした実用データサイエンスの基盤がほぼ完成します。 「データを語る言語」としての JSON を、 ぜひ自分の道具として身につけてください。
100 MB を超える JSON を扱うとき、 単一ファイルは非効率です。 「日付別」「都道府県別」「カテゴリ別」のいずれかでパーティショニングするのが定番。 SSDSE-B のような時系列データなら「年度別ディレクトリ + NDJSON」が扱いやすい形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # ── この抜粋で使うデータを用意します ── import os, gzip, json import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) os.makedirs('outputs/partitioned', exist_ok=True) for year, sub in df.groupby('年度'): path = f'outputs/partitioned/year={year}/data.ndjson.gz' os.makedirs(os.path.dirname(path), exist_ok=True) with gzip.open(path, 'wt', encoding='utf-8') as f: for _, row in sub.iterrows(): f.write(json.dumps(row.to_dict(), ensure_ascii=False, default=str) + '\n') print('作成したディレクトリ:', sorted(os.listdir('outputs/partitioned'))[:3], '...') # year=2012/, year=2013/, ..., year=2023/ のディレクトリ構造 # Hive 互換のパーティション形式で BigQuery 等から直接クエリ可 |
💬 表示されたのは先頭 3 つの year=2012〜2014 だけだが、実際には 2012〜2023 年度の 12 ディレクトリができ、各 data.ndjson.gz に 47 県が 1 行 1 レコードで入る(2023 年度分は圧縮後で約 26 KB)。年度で絞らずに 564 行を 1 ファイルにする代わりに、年度ごとに分けておけば「2023 年度だけ読む」ときに他の 11 ファイルを開かずに済む。
この形式は AWS S3 / Google Cloud Storage に置けば、 Athena / BigQuery から SQL で集計可能。 SSDSE-B 程度の小データなら単一ファイルで十分ですが、 数十 GB に膨れたときの対処法として覚えておく価値があります。
演習1: SSDSE-B-2026 を読み込み、 2023 年の 47 県データを data/processed/ssdse_b_2023.json として保存せよ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import pandas as pd import json import os # 1. CSV 読み込み df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) # 2. 2023年でフィルタ df23 = df[df['年度']==2023].copy() # 3. 必要列を選択 + 高齢化率を派生列に df23['高齢化率'] = (df23['65歳以上人口'] / df23['総人口'] * 100).round(2) # 実際の列名は「都道府県名」ではなく「都道府県」 df23 = df23[['地域コード','都道府県','総人口','65歳以上人口','高齢化率']] df23.columns = ['code','prefecture','population','aged_pop','aged_ratio'] # 4. JSON 構造を作る out = { 'metadata': { 'source': 'SSDSE-B-2026', 'year': 2023, 'rows': len(df23), 'columns': df23.columns.tolist(), 'total_population': int(df23['population'].sum()), 'mean_aged_ratio': round(df23['aged_ratio'].mean(), 2), }, 'data': df23.to_dict(orient='records'), } # 5. 保存 os.makedirs('data/processed', exist_ok=True) with open('data/processed/ssdse_b_2023.json','w',encoding='utf-8') as f: json.dump(out, f, ensure_ascii=False, indent=2) print('保存完了: data/processed/ssdse_b_2023.json') # 検証 with open('data/processed/ssdse_b_2023.json',encoding='utf-8') as f: loaded = json.load(f) assert loaded['metadata']['rows'] == 47 assert len(loaded['data']) == 47 print('検証 OK') |
💬 表示は 2 行だけだが、assert が 2 本とも通ったので metadata の rows と data 配列の長さがどちらも 47 だと確認できている。metadata には全国人口 124,353,000 人と、47 県の高齢化率を単純平均した 31.59% も入る。単純平均は県の人口の重みを無視した値なので、全国の高齢化率(65 歳以上人口の合計 ÷ 総人口の合計)とは一致しない点に気を付けたい。
この一連のコードは、 「読み込み → 加工 → 構造化 → 保存 → 検証」 の流れを完備しており、 他の演習でも同じ骨格を使い回せます。
🍰 まずはやさしく
データの受け渡しに使う共通のルールです。
ネット上のデータを集めるために使います。
統計サイトからデータを取る時に出会います。
どんな場面でJSONが登場するかを読みます。
JSON は Web API のレスポンス、 e-Stat の統計データ取得、 設定ファイル (package.json, .vscode/settings.json)、 NoSQL データベース (MongoDB) などあらゆる場面で登場します。 SSDSE-B-2026 は CSV 配布ですが、 同じデータを e-Stat の API で取得すると JSON 形式で返ります。 つまり「同じ統計データを 2 つのフォーマットで扱う」基礎技能としても、 JSON は必須知識です。
🍰 まずはやさしく
データの整理棚のような仕組みです。
分析しやすい形に整えるために使います。
部活の名簿を整理する感覚に似ています。
JSONの構造や使い方のコツを読みます。
データを「分析・モデリングに使える形に整える」工程。 分析の質はここで 8 割決まります。
本ページでは JSON を、 定義・前提条件・使い方・落とし穴の順に整理して解説します。 厳密な定義より、 まず何を、 いつ、 どう使うかを理解することを優先してください。
JSON の理解には、 (1) 階層構造の木表現 (オブジェクト/配列のネスト)、 (2) スキーマ検証時の分布チェック、 (3) 統計データとの相関構造保持 の 3 点を同時に把握する必要があります。 ここでは SSDSE-B-2026 の 2023 年度 47 都道府県を実際に JSON にした図を使って、 JSON データ処理のキャンバスを描き出します。
{"prefectures": [{"name": "北海道", "stats": {"pop": 5092000, "births": 24430}}, ...]} の形で JSON にし、 先頭 2 県(北海道・青森県)と最後の沖縄県だけを展開した。 ルートの { }(深さ 0)→ 47 要素の配列(深さ 1)→ 各県のオブジェクト(深さ 2)→ "name" と "stats"(深さ 3)→ "pop"・"births" の数値(深さ 4、 リーフ)と 4 段のネストになる。 兄弟ノードが同じ階層の要素を表し、 jq や JSONPath での参照パス(例 $.prefectures[46].stats.pop → 1,468,000)は、 この木を上から辿る経路指定である。読み取るポイント:
{"name": ..., "stats": {"pop": ..., "births": ...}} の形で JSON にして戻し、 stats.pop(総人口)× stats.births(出生数)を描くと、 正しく展開できていれば r = 0.995 の直線に並ぶ(左)。 births の配列が 1 行ずれて結合されると同じデータでも r = 0.475 に崩れる(右)。 強い相関が確認できれば、 スキーマ設計時に正規化が必要かも判断できる。 SSDSE-B-2026 を JSON 化した後、 pandas で pd.json_normalize() し、 散布図でデータ整合性をチェックするのが標準フロー。 異常値や型崩れも散布図で検知しやすい。読み取るポイント:
stats.pop として JSON に書き出して読み戻し、 CSV の値(青)と重ねると、 最大差 0 で度数が完全に一致する(中央値 155 万人、 東京都 1,409 万人が右端に離れる右偏の分布)。 これがフラットに見えるなら型変換ミス (文字列→数値の失敗) を疑う。 JSON Schema の "type": "number" 検証と並行して、 必ず分布チェックを行う。読み取るポイント:
この 3 枚を順に追うと、 「構造把握 → 関係性確認 → 分布検証」 という JSON データ品質チェックのフローが体系化される。 関連用語 API / CSV / データクレンジング へ進むと、 各図の理論背景を深掘りできる。
JSON 解析後の典型的な可視化フロー(分布 → 散布 → 構造)を、 SSDSE 由来の参照図で確認する。 これらは JSON から DataFrame に変換した後の品質チェックや関係性把握に使われる定番図。



→ JSON は構造データだが、 解析の本質は「数値列の分布と関係性の確認」。 ヒストグラム・散布図・相関行列の 3 点セットで、 ほとんどの初期 EDA は完了する。
json.loads() と json.dumps() の役割をそれぞれ 1 行で説明できるか?pd.json_normalize)null は pandas で何に変換されるか?→ すべて即答できれば、 JSON を実務でハンドリングする基礎力は十分。 不安な項目は本ページの該当セクションを再読すること。
JSON は SSDSE-B などの構造化データを階層・行列として扱う基盤フォーマット。 ここでは関連する 3 つの視覚資料で「データ全体の形」を再確認する。
{"name": "秋田県", "series": [{"year": 2012, ...}, ...]} のように県の下に年度の配列をネストすると表現が直感的
JSON を扱うときに最も重要なのは、 「シリアライズ → デシリアライズ → 解析」 の過程で情報が失われていないかを確認することです。 ここでは SSDSE-B-2026 由来の数値を JSON 経由で取り扱った後の確認手段を、 3 つの定番図で説明します。

stats.pop = 総人口) を pandas に展開し、 ヒストグラムで分布を確認する。 元 CSV(青)と JSON 往復後(破線)の度数が一致すれば JSON 化は成功。 ヒストグラムが平坦化したり、 ピークが消えたりしたら、 文字列としてシリアライズされた数値が混入している可能性が高い。 これは json.dump(..., default=str) の副作用としてよく発生する。
{"stats": {"pop": ..., "births": ...}} のような構造から散布図を作ると、 ドメイン的に意味のある相関 (人口 ↔ 出生数の正相関、 2023 年度で r = 0.995) が再現されているかを確認できる。 配列が 1 行ずれて結合されると r = 0.475 まで落ちる。 散布図に異常なクラスタや境界線が出る場合は、 ネスト構造を pd.json_normalize で展開する際の階層エラーを疑う。
この 3 図のチェックを毎回必ず挟むだけで、 JSON 由来のデータ品質バグの多くを早い段階で見つけられます。 とくに API クライアントから取得した JSON は、 サーバー側のバグ・型変換ミス・スキーマ更新の取りこぼしが混入しやすく、 分布検証なしで分析を始めると後工程で結論を覆すコストを支払うことになります。
| 確認段階 | 使う図 | 検知できる問題 | 対応 |
|---|---|---|---|
| 単変量分布 | ヒストグラム | 型崩れ、 文字列混入、 欠損増加 | dtype 検査、 NaN 集計 |
| 2 変量関係 | 散布図 | ネスト構造の展開ミス、 行のずれ | json_normalize 経路再確認 |
| 多変量俯瞰 | 相関ヒートマップ | 冗長フィールド、 派生指標の重複 | スキーマ正規化、 派生列を除外 |
これら 3 図はそれぞれ単純な可視化ですが、 「JSON のシリアライズが情報量を保存できているか」 という根本的な品質確認を、 視覚的かつ網羅的に行える手段として実務で重宝されます。 とくに e-Stat 風 API や社内 REST API を設計する際は、 受信側の最初のステップとして必ず組み込むべきチェックポイントです。
JSON は 「読み書きしやすい」 反面、 運用面の罠 が多い形式です。 ここでは公開 API・社内 API・データ永続化の 3 シーンで頻出する運用パターンを、 実データを念頭に整理します。
e-Stat、 政府統計、 自治体 API などはほぼ JSON で提供されます。 SSDSE-B-2026 を pd.json_normalize で取得した後、 必ず df.dtypes でカラム型を確認しましょう。 とくに人口・面積などの数値が object (文字列) になっている場合、 後工程の集計・可視化が静かに失敗します。 さらに JSON の null は pandas で NaN または None に変換されるため、 集計時の挙動 (mean/sum/count) を意識する必要があります。
社内マイクロサービス間で JSON を使う場合、 「JSON Schema で型を契約する」 ことが鉄則です。 Schema を書かないと、 上流のスキーマ変更が下流に伝わらず、 ある日突然 KeyError や型エラーで本番が止まります。 OpenAPI と組み合わせれば、 リクエスト/レスポンスの両方を機械的に検証でき、 ドキュメントとコードの乖離も防げます。 さらに jsonschema パッケージで CI に組み込み、 PR 時点でスキーマ違反を検知する運用が安全です。
JSON を直接データレイクに置く運用は非推奨です。 大規模化すると JSON はパース速度・サイズ効率で Parquet/ORC に大きく劣ります。 暫定保存には NDJSON (1 行 1 JSON) + gzip を使い、 解析段階では Parquet に変換するハイブリッド戦略が一般的。 SSDSE-B-2026 規模 (47 県 × 110 列) なら JSON 直置きでも問題ありませんが、 数年分の積算で時系列化する場合は早い段階で Parquet 化を検討すべきです。
| シーン | 推奨フォーマット | 理由 | 注意点 |
|---|---|---|---|
| 公開 API | JSON (UTF-8) | クライアント互換性、 ブラウザネイティブ | 数値型崩れ、 null 仕様 |
| 社内サービス間 | JSON + Schema 検証 | 柔軟性と型契約の両立 | スキーマ更新の同期 |
| 大規模解析 | Parquet / Arrow | 列指向で速く、 圧縮効率も高い | 人間可読性の喪失 |
| ストリーミング | NDJSON (JSON Lines) | 追記しやすい、 部分読みやすい | 配列としては読めない |
これらのパターンを使い分ける感覚は、 1 度プロダクションで JSON のスキーマ変更事故を経験すると一気に身につきます。 平常時の予防として、 必ず最初に分布チェック・型チェック・スキーマ検証の 3 点セットを入れる癖をつけてください。
日本語を含む JSON では、 ensure_ascii=True (Python の json.dump のデフォルト) のままにすると、 都道府県名や注釈が 京都 のようにエスケープされ、 ファイルサイズが約 2 〜 3 倍に膨らみます。 SSDSE-B-2026 の都道府県カラムを JSON 化する場合は、 ほぼ確実に ensure_ascii=False + UTF-8 ファイル書き出し (open(path, 'w', encoding='utf-8')) の組み合わせを使うべきです。 逆に、 古い Windows 環境や厳格な ASCII-only API では、 デフォルトのままの方が安全な場合もあります。 「文字コードはどこで決まるか」を意識せずに JSON を回すと、 数年後に文字化けでデータが復元不可能になる事故が起きるため、 必ずヘッダ・ファイル・通信経路の 3 段階で UTF-8 を統一する運用にしてください。
また日本語キー (例: {"都道府県": "東京都"}) も RFC 8259 上は合法ですが、 多くの JSON Schema バリデータ・OpenAPI ジェネレータが正しく扱えないケースがあり、 推奨はキーは ASCII、 値は日本語可のパターンです。 これは SSDSE-B-2026 の CSV を JSON 化する際の暗黙のルールとして覚えておきましょう。
標準ライブラリの json モジュールは便利ですが、 数十万件規模になるとパース速度が体感で詰まってきます。 orjson や ujson は C/Rust 実装で 5 〜 10 倍速く、 とくに orjson は datetime・numpy 配列・dataclass を直接シリアライズできるため、 SSDSE-B-2026 のような数値ばかりの DataFrame を JSON 化する用途では強力です。 ただし orjson は厳格な RFC 準拠で、 標準 json が許容する一部の非標準 (NaN・Infinity) を拒否するので、 既存パイプラインを単純置換すると例外が出る場合があります。
さらに数百 MB を超える JSON は ijson によるストリーミングパース、 または NDJSON で 1 行ずつ json.loads する戦略が現実的です。 1 ファイル全体を json.load(f) でメモリに読み込もうとすると、 RAM の数倍を消費してプロセスが落ちる典型的なバグになります。 規模が見えない JSON はストリーミングで読む を第一手にしてください。 SSDSE-B-2026 規模なら一括読み込みで問題ありませんが、 e-Stat 全テーマ × 全年度を 1 ファイルに束ねる場合は数 GB に到達し、 一括読み込みは現実的でなくなります。
最後に、 JSON のサイズ最適化として indent=None (改行・スペースなし) でシリアライズすると、 SSDSE-B-2026 の 1 県分で約 30% サイズが減ります。 人間が読まない API レスポンスではこれが標準。 デバッグ用には indent=2 を使い、 本番では indent=None + separators=(',', ':') を選ぶことで、 通信量を最小化できます。
本ページで JSON の基礎構造・読み書き・パフォーマンス・運用パターンを押さえました。 次に学ぶべきは JSON Schema (型契約の言語)、 OpenAPI 3.x (REST API の設計仕様)、 そして JSON-LD (Semantic Web・Schema.org への対応) です。 これらを順に押さえると、 単に 「JSON を読み書きする」 段階から、 「JSON を中心に API・データ基盤・SEO まで設計する」 段階に進めます。
並行して、 API・CSV・データクレンジング・DataFrame の各用語ページを巡回すると、 「JSON で受け取る → DataFrame で整形 → クレンジング → 集計」 という一連の流れを体系的に理解できます。 SSDSE-B-2026 の小規模データでこのフローを 1 周通すことが、 実務に直結する最短経路です。
最後に、 JSON を 「単なるデータ転送フォーマット」 と捉えるのではなく、 「世界中の API・ログ・設定ファイルの共通言語」 として捉えることをお勧めします。 そう捉えると、 JSON Schema・OpenAPI・JSON-LD・NDJSON といった派生規格を学ぶ動機が明確になり、 自分の手で 「API を設計する側」 に回るための道筋が見えてきます。 SSDSE-B-2026 から取り出した数値を、 JSON 経由で世界へ発信する力を、 ぜひこのページから身につけてください。 そしてその先には API 設計者・データエンジニアとしての具体的なキャリアパスが広がっています。
🍰 まずはやさしく
データの書き方を決めた世界共通の形式です。
データを正しく伝える道具として使います。
プログラミングの学習などでよく使われます。
JSONの定義と使う時の条件について読みます。
構造化データの軽量交換フォーマット
英語名 JavaScript Object Notation。
この用語を理解・使用するときは、 次のような前提を意識してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # JSON Schema で型と必須項目を検証 import jsonschema, json schema = { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object", "required": ["code","prefecture","population"], "properties": { "code": {"type":"string","pattern":"^R[0-9]{5}$"}, "prefecture": {"type":"string"}, "population": {"type":"integer","minimum":0}, "aged_pop": {"type":"integer","minimum":0}, "aged_ratio": {"type":"number","minimum":0,"maximum":100} } } } } } with open('data/processed/ssdse_b_2023.json',encoding='utf-8') as f: data = json.load(f) jsonschema.validate(data, schema) print('Schema OK') |
💬 「Schema OK」は、47 件すべてが code・prefecture・population を持ち、コードが R+5 桁、人口が 0 以上の整数、高齢化率が 0〜100 に収まっていたという意味。validate は最初の違反で例外を投げるので、何も表示されずに止まらなかったこと自体が合格の印になる。一方で metadata や data 自体は required に入っていないので、data キーが丸ごと欠けた JSON もこのスキーマは通してしまう。
| フォーマット | 特徴 | 用途 |
|---|---|---|
| JSON | 標準 | Web API |
| JSON Lines (NDJSON) | 1 行 1 オブジェクト | BigQuery, ログ |
| JSON5 | コメント・末尾カンマOK | 設定ファイル |
| BSON | バイナリ JSON | MongoDB |
| MessagePack | コンパクト | モバイル通信 |
| JSON-LD | リンクトデータ | Schema.org, SEO |
JSON は数式ではなくデータ構造ですが、 形式言語として厳密な文法 (BNF) を持ち、 再帰的な定義として読み解けます。 RFC 8259 で定義された JSON のグランマールは以下のように再帰的に表現できます: $$\textit{value} \to \textit{object}\ |\ \textit{array}\ |\ \textit{string}\ |\ \textit{number}\ |\ \textit{boolean}\ |\ \textit{null}$$ $$\textit{object} \to \{\ \textit{string}\ :\ \textit{value},\ \dots\ \}$$ $$\textit{array} \to [\ \textit{value},\ \textit{value},\ \dots\ ]$$ この 6 種類の基本型と 2 種類のコンテナ (object / array) の組み合わせだけで、 任意の階層的データを表現します。 シンプルさが普及の最大要因です。
JSON の基本型はstring (UTF-8 のテキスト、 ダブルクォート必須)、 number (整数・浮動小数、 ただし NaN/Infinity 非対応)、 boolean (true / false)、 null (欠損)、 object (キー=string の連想配列)、 array (順序付きリスト) の 6 種類のみ。 これは Python の int/float/str/bool/None/dict/list に概ね対応します。 注意点として、 JSON には日付型がないため、 慣習的に ISO 8601 文字列 (例: "2023-12-31T23:59:59Z") を使います。 また、 JavaScript の Number は 2^53-1 までしか整数を厳密に表現できないため、 64-bit ID(マイナンバーや Twitter Snowflake ID)は string で送るのが安全です。
object は {"key1": value1, "key2": value2} の形式で、 キーは必ず string、 値は任意の type。 「キーの順序は保証されない」のが標準 (Python の dict は 3.7+ で挿入順保証だが、 JSON 仕様としては不定)。 SSDSE-B-2026 を JSON で表現するなら、 {"prefecture":"東京都", "population":14086000, "aged_ratio":22.75} のように、 1 都道府県を 1 object で表します。 object はネスト可能で、 {"metadata":{"year":2023,"source":"SSDSE"}} のように階層構造を任意に深くできます。
array は [value1, value2, value3] で、 順序が保持され、 異なる型を混在可能。 47 都道府県を JSON で持つなら "data": [{"prefecture":"北海道",...}, {"prefecture":"青森県",...}, ...] のように 47 個の object を array に並べます。 array の要素は同一構造である必要は無いが、 実務的にはスキーマを統一する方が後段の処理が楽。 SSDSE のような「同質的なレコード集合」は array of objects のパターンで表現するのが定石です。
value は再帰的に object / array を含めるため、 任意の深さの階層構造が表現可能。 例: {"data":[{"prefecture":"東京都","subregions":[{"name":"23区","pop":9700000},{"name":"多摩","pop":4200000}]}]} のように、 都道府県の中に下位地域を入れ子にできます。 ただし、 ネストが深くなると可読性と処理速度が低下するため、 実用上は 3〜4 階層が上限。 BigQuery などの分析エンジンは深いネストを苦手とするため、 平坦化 (flatten) する場面も多い。 SSDSE-B-2026 程度のフラットなデータなら、 1 階層 (オブジェクトの配列) で十分です。
SSDSE-B-2026 は CSV だが、 Web API では JSON が事実上の標準。 e-Stat の API レスポンスも JSON。 同じ都道府県データを JSON 化するとこうなる:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | {
"metadata": {
"source": "SSDSE-B-2026",
"year": 2023,
"rows": 47,
"encoding": "utf-8"
},
"data": [
{"code": "R13000", "prefecture": "東京都",
"population": 14086000, "aged_pop": 3205000,
"aged_ratio": 22.75},
{"code": "R01000", "prefecture": "北海道",
"population": 5092000, "aged_pop": 1681000,
"aged_ratio": 33.01},
{"code": "R47000", "prefecture": "沖縄県",
"population": 1468000, "aged_pop": 350000,
"aged_ratio": 23.84}
]
}
|
CSV と比較した特徴:
| 観点 | CSV | JSON |
|---|---|---|
| 階層構造 | 平坦のみ | ネスト可 |
| 型情報 | 全て文字列 | 数値・真偽・null 区別 |
| スキーマ | 暗黙的 | JSON Schema で記述可 |
| サイズ | 小さい | キー名繰り返しで大きい |
| 可読性 | 表形式向き | ネスト向き |
| Web API | ファイル添付 | レスポンスボディ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # pandas → JSON 変換 import pandas as pd import json df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) df = df[df['年度']==2023][['地域コード','都道府県','総人口','65歳以上人口']] df.columns = ['code','prefecture','population','aged_pop'] df['aged_ratio'] = (df['aged_pop']/df['population']*100).round(2) # orient='records' で配列の各要素が 1 都道府県 records = df.to_dict(orient='records') out = {'metadata': {'source':'SSDSE-B-2026','year':2023,'rows':len(df)}, 'data': records} with open('ssdse_b_2023.json','w',encoding='utf-8') as f: json.dump(out, f, ensure_ascii=False, indent=2) # 読み込み with open('ssdse_b_2023.json',encoding='utf-8') as f: loaded = json.load(f) print(loaded['data'][0]) # {'code':'R01000','prefecture':'北海道',...} |
💬 orient='records' にしたので data[0] は北海道 1 県分の辞書になり、1,681,000 ÷ 5,092,000 × 100 を round(2) した 33.01 が aged_ratio に入っている。json.dump の段階で numpy の int64 が Python の int に変わっているため、読み戻した population は 5092000 のまま型エラーなく扱える。ensure_ascii=False を外すとファイル内の「北海道」が \u5317… のエスケープ列になるので、目で確かめたいときは付けておく。
data/raw/SSDSE-B-2026.csv から 2023 年の 47 都道府県データ。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import pandas as pd import json df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df23 = df[df['年度']==2023].copy() df23['aged_ratio'] = (df23['65歳以上人口']/df23['総人口']*100).round(2) # 実際の列名は「都道府県名」ではなく「都道府県」 df23 = df23[['地域コード','都道府県','総人口','65歳以上人口','aged_ratio']] df23.columns = ['code','prefecture','population','aged_pop','aged_ratio'] # JSON 構造を作る out = { 'metadata': { 'source': 'SSDSE-B-2026', 'year': 2023, 'rows': len(df23), 'encoding': 'utf-8', }, 'data': df23.to_dict(orient='records'), } import os os.makedirs('outputs', exist_ok=True) # 保存先が無いと open('w') は失敗する with open('outputs/ssdse_b_2023.json','w',encoding='utf-8') as f: json.dump(out, f, ensure_ascii=False, indent=2) # 読み戻し with open('outputs/ssdse_b_2023.json',encoding='utf-8') as f: loaded = json.load(f) print(loaded['metadata']) print(loaded['data'][0]) # 北海道 print(loaded['data'][12]) # 東京都 print(f'全国合計 = {sum(d["population"] for d in loaded["data"]):,}') |
ensure_ascii=False が日本語を「東京都」のまま保存する鍵。 デフォルトの True だと "\u6771\u4eac\u90fd" のようなエスケープ列になる。 保存された outputs/ssdse_b_2023.json は indent=2 で約 7.1 KB(7,228 byte)。 data[12] が東京都になるのは、地域コード順に北海道を 0 番として 13 番目だから。 また orient='records' は object の array 形式(API レスポンスの定番)。 他にも 'index'、 'split'、 'columns' など 6 種のフォーマットがある。| 業界 | JSON の役割 | 代表例 / 規模 |
|---|---|---|
| Web API (一般) | REST API のレスポンスフォーマットの事実上の標準。 Twitter, Stripe, GitHub, e-Stat, 気象庁すべて JSON。 | 数 KB〜数 MB |
| 公的統計 | e-Stat API は 統計データを JSON で配信。 都道府県別人口・GDP・気象データ等。 SSDSE-B も裏側は同じソース。 | 数 KB〜数十 MB |
| NoSQL DB | MongoDB / Couchbase は JSON ドキュメントを保存・検索する DB。 スキーマレスな柔軟性。 | 数百GB |
| ログ・分析基盤 | BigQuery / Snowflake は JSON Lines (NDJSON) でログを取り込み。 1 行 1 オブジェクト。 | TB 級/日 |
| 設定ファイル | package.json (Node.js)、 tsconfig.json、 .vscode/settings.json など、 開発ツールの設定。 | 数 KB |
| 機械学習モデル交換 | scikit-learn / Hugging Face Transformer のメタデータ、 ONNX のモデル属性は JSON で記述。 | 数 KB〜数 MB |
| フォーマット | 階層 | 型情報 | サイズ | 人間可読 | 用途 |
|---|---|---|---|---|---|
| JSON | ○ | ○ | 中 | ○ | Web API 標準 |
| CSV | × | × | 小 | ○ | 表形式データ、 Excel |
| XML | ○ | スキーマ依存 | 大 | 中 | SOAP、 設定 (.NET) |
| YAML | ○ | ○ | 小 | ◎ | 設定 (Kubernetes, GitHub Actions) |
| TOML | △ | ○ | 小 | ◎ | pyproject.toml、 Cargo.toml |
| JSON Lines (NDJSON) | ○ | ○ | 中 | ○ | ログ、 BigQuery 取り込み |
| Parquet | ○ | ◎ | 最小 | × | 分析、 大規模列志向 |
| MessagePack | ○ | ○ | 小 | × | バイナリJSON、 高速通信 |
| Protobuf | ○ | ◎ | 最小 | × | gRPC, Google 系 |
| Avro | ○ | ◎ | 最小 | × | Hadoop、 Kafka |
data/processed/ssdse_b_2023.json として保存せよ (metadata + data の構造)。1行1オブジェクト) で保存し、 ファイルサイズを通常 JSON と比較せよ。{"地域":"関東","都道府県":[{...},{...}]})。json.dump のデフォルトは ensure_ascii=True。 「東京都」を保存しても「東京都」になり、 ファイルが膨らみ可読性が消える。 対策: 必ず ensure_ascii=False を指定し、 UTF-8 で書き出す。
df.to_json() で NaN は null に変換される。 復号後 None として扱われるが、 集計関数 (sum, mean) の挙動が変わる。 対策: 事前に欠損方針を決め、 必要なら fillna してから JSON 化。
マイナンバーや Twitter Snowflake ID のような 18 桁整数を JSON の Number として送ると、 JavaScript 側で 2^53-1 を超え末尾が丸まる。 対策: ID は文字列にキャストして送る。
JSON はコメント非対応。 設定ファイルにメモを書こうとして // comment を入れると即 parse エラー。 対策: JSON5 か YAML を使う、 もしくは "_comment": "..." 風のキーで誤魔化す。
JavaScript リテラルでは許容される末尾カンマが JSON では文法エラー。 手書きで JSON を整形すると頻発する事故。 対策: lint ツールで自動チェック、 または手書きを避けて json.dumps で生成。
/data/0/prefecture = data 配列の最初の prefecture 値。JSON は 2001 年に Douglas Crockford が公開しました。 JavaScript のオブジェクトリテラル構文をそのまま使うシンプルさが当初の特徴です。 当時は XML が事実上の標準で、 SOAP / WSDL などの重厚なプロトコルが Web API の主流でした。 しかし XML は冗長で、 ブラウザ側 JavaScript からは扱いにくい。 そこで「JavaScript で eval() するだけでオブジェクトになる」JSON が、 AJAX (2005年) ブームと共に急速に普及しました。
2006 年に RFC 4627 として標準化、 2013 年に RFC 7159、 2017 年に RFC 8259 として現行版になりました。 JSON Schema は 2009 年から開発が始まり、 現在は draft-2020-12 まで進化。 NoSQL ムーブメント (MongoDB 2009, CouchDB 2005) と相まって、 「スキーマレスで階層的なデータ」を扱う標準として完全に定着しました。
日本の e-Stat も 2014 年に API を公開し、 JSON / XML 両形式でデータを配信。 SSDSE-B-2026 のような統計データを CSV で配布する一方、 同じデータが JSON API でも取れるようになっており、 「データ取得の現代的標準」として JSON は不動の地位を占めています。
1 2 3 4 5 6 7 8 9 10 11 | import requests url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData' params = { 'appId': 'YOUR_APP_ID', 'statsDataId': '0003448237', # 人口推計 'cdArea': '13000', # 東京都 'limit': 100, } res = requests.get(url, params=params) data = res.json() print(data['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'][:3]) |
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd nested = { 'metadata': {'source':'SSDSE-B-2026','year':2023}, 'data': [ {'prefecture':'東京都','population':14086000,'subregions':[{'name':'23区','pop':9700000},{'name':'多摩','pop':4200000}]}, {'prefecture':'大阪府','population':8763000,'subregions':[{'name':'大阪市','pop':2750000}]}, ] } # 1段目を展開 df = pd.json_normalize(nested['data'], record_path='subregions', meta=['prefecture','population']) print(df) |
💬 subregions の要素数だけ行が展開され、東京都は 2 行、大阪府は 1 行になった。meta に指定した population は親の値がそのまま繰り返されるので、東京都の 14,086,000 が 2 行に重複している。この表で population を合計すると東京を二重に数えることになり、また 23 区と多摩の合計 1,390 万人は島しょ部を含まないため都の総人口とは一致しない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import jsonschema schema = { "type": "object", "required": ["metadata","data"], "properties": { "metadata": {"type":"object", "required":["source","year"], "properties":{"source":{"type":"string"},"year":{"type":"integer","minimum":2000}}}, "data": {"type":"array", "items":{"type":"object","required":["prefecture","population"], "properties":{"prefecture":{"type":"string"}, "population":{"type":"integer","minimum":0}, "aged_ratio":{"type":"number","minimum":0,"maximum":100}}}} } } jsonschema.validate(loaded, schema) print('Schema OK') |
💬 こちらのスキーマは metadata と data を required にし、year が 2000 以上の整数であることまで確かめている。前の節のブロックで作った loaded は metadata.year = 2023、data が 47 件なのでそのまま通る。キーの存在まで縛ると、上流の変換で配列ごと落ちた JSON を読み込み時点で弾ける。
1 2 3 4 5 6 7 8 | # pip install jmespath import jmespath # data 配列から population 1000万超のレコードを抽出 # JMESPath では英数字以外のキー名は二重引用符で囲む必要がある big = jmespath.search( 'data[?population > `10000000`].{"県": prefecture, "人口": population}', loaded) print(big) # → [{'県':'東京都','人口':14086000}](2 位の神奈川県は 922.9 万人で条件に届かない) |
💬 条件を満たしたのは東京都(14,086,000 人)の 1 件だけで、結果は要素 1 つのリストになる。2 位の神奈川県は 9,229,000 人で 1,000 万人に約 77 万人届かない。JMESPath では数値をバッククォートで `10000000` と書く必要があり、囲み忘れると構文エラー、シングルクォートで囲むと文字列扱いになって Python 版では int と str の比較で TypeError になる。
1 2 3 4 5 6 7 8 9 10 11 12 | import os os.makedirs('outputs', exist_ok=True) # 保存先のフォルダを作っておく # 47県を 1 行 1 オブジェクトで保存 df23.to_json('outputs/ssdse_b_2023.ndjson', orient='records', lines=True, force_ascii=False) # 読み込みもストリーミング可能 import pandas as pd records = [] with open('outputs/ssdse_b_2023.ndjson',encoding='utf-8') as f: for line in f: records.append(json.loads(line)) print(len(records), records[0]) |
💬 NDJSON は 1 行 1 レコードなので、ファイルを 1 行ずつ json.loads して 47 件がそのまま戻った。先頭の北海道のレコードは orient='records' で作った辞書と同じ 5 項目・同じ値(aged_ratio 33.01)で、書式を変えても中身が崩れていないことが分かる。行単位で読めるため、巨大なログでも全体をメモリに載せずに先頭から処理できるのが通常の JSON 配列との違い。
json.load でメモリに展開して OOM。 → ijson でストリーミング、 または NDJSON 化。a={'x':1}; a['self']=a; json.dump(a) は無限再帰。 → 平坦化または ID 参照。TypeError: Object of type datetime is not JSON serializable。 → ISO 8601 文字列に変換。eval(jsonStr) は危険。 必ず JSON.parse を使う。ijson ライブラリでイベント駆動パース。 もしくは JSON Lines (NDJSON) 形式にして 1 行ずつ処理。合成データでネスト構造の JSON サイズを計算する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import json # Step 1: 1 レコードのスキーマ(架空の会員データ)と件数 record = {'id': 1234, 'name': 'Sato', 'age': 34, 'addr': {'city': 'Hiroshima', 'zip': '731-5193'}, 'tags': ['stat', 'python', 'json']} n_records = 10_000 bytes_per_record = 100 # Step 1 の見積もり gzip_ratio = 0.30 # 圧縮後 / 圧縮前 の見積もり # 見積もりの 100 byte が妥当かを 1 件で確かめる actual = len(json.dumps(record, separators=(',', ':')).encode('utf-8')) print(f'1 レコードの実サイズ: {actual} byte') # Step 2: 全体サイズ total_bytes = bytes_per_record * n_records print(f'JSON: {total_bytes/1024:.0f} KB') print(f'gzip: {total_bytes*gzip_ratio/1024:.0f} KB') print(f'配列要素: {n_records * len(record["tags"])}') |
💬 977 KB・293 KB・30000 個は Step 2 の手計算と一致する。見積もりの 100 byte に対し、例として作った 1 件を区切りの空白なしで書き出すと 111 byte で、実際には 1 割ほど大きくなる。gzip の 0.30 も仮の比率で、同じキー名が 1 万回繰り返す JSON では実測するとこれよりずっと小さくなることが多いので、容量を見積もるときは数百件を実際に圧縮して比率を測っておく。
SSDSE-B-2026 のような公的統計データを Python で扱う際の基本パターン:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import json import pandas as pd # データ読み込み(skiprows=[1] で英字コードの列名。564 行 = 47 県 × 12 年度) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) print(df.shape) print(df.dtypes.value_counts()) # 列の型の内訳 # 2023 年度だけに絞り、北海道の総人口 1 セルを JSON にできるか試す df23 = df[df['SSDSE-B-2026'] == 2023] v = df23['A1101'].iloc[0] print(type(v).__name__, v) try: json.dumps({'A1101': v}) except TypeError as e: print('TypeError:', e) print(json.dumps({'A1101': int(v)})) # Python の int に直せば通る # pandas の to_json は numpy の型を JSON の number に直してくれる print(df23[['Code', 'Prefecture', 'A1101', 'A1303']].head(2) .to_json(orient='records', force_ascii=False)) |
💬 564 行 × 112 列のうち 104 列が int64、6 列が float64、文字列は Code と Prefecture の 2 列だけ。北海道の総人口 5092000 は見た目は整数でも numpy の int64 なので、json.dumps にそのまま渡すと TypeError になり、int() で Python の int に直すと通る。to_json は内部でこの変換をしてくれるため、DataFrame から書き出すときは 5092000・1681000 がそのまま JSON の number になる。
具体的なコードは データエンジニアリング を参照してください。
分析結果を報告するときに含めるべき情報:
左のテキストを編集すると即座に JSON.parse による構文チェックが走り、正しければ右に色分けツリーが、間違っていればエラー位置と「JSON 特有の落とし穴」の原因推定が表示される。▾ をタップするとネスト階層を折りたためる。壊れた JSON のプリセットボタンで、末尾カンマ・シングルクォート・引用符なしキーなどの典型エラーを実際に体感してみよう。
| リーフ値の数 | — |
| キーの総数 | — |
| 最大ネスト深さ | — |
| UTF-8 バイト数 | — |
JSON の文法は驚くほど小さい。 {}(オブジェクト = キーと値の辞書)と[](配列 = 順序付きリスト)の 2 つの容れ物に、文字列・数値・真偽値・null の 4 種のスカラーを入れ子にするだけ。 CSV が「平坦な表」しか運べないのに対し、JSON は上のサンプルのように「metadata(辞書)+ data(レコードの配列)」という階層をそのまま 1 つのテキストで運べる。 ツリー表示の ▾ を折りたたんで全体の骨格だけを眺めると、「辞書と配列の入れ子」という構造が一目で掴める。
[1, 2, 3,] が許されるが、JSON では } や ] の直前のカンマは構文エラー。手書き JSON の最頻出ミス。// や /* */ は存在しない。設定ファイルにコメントが欲しければ JSON5・JSONC・YAML など別フォーマットを検討する。"..."。シングルクォートも、キーの引用符省略({name: "x"})も JS では合法だが JSON では不可。0.1 + 0.2 = 0.30000000000000004 という浮動小数の誤差と、253 を超える整数 ID がパース時に丸められて末尾の桁が変わってしまう様子をツリーで確認できる。だから 18 桁級の ID や金額は文字列で送るのが実務の鉄則。null で表す(Python の True/False/None をそのまま書くのも不可)。JSON Lines(NDJSON)は「1 行 = 1 個の JSON」を改行で並べる形式で、全体を [...] で包まないため、巨大データでも 1 行ずつストリーミング処理でき、ログ収集や機械学習データセット(例: 大規模コーパス配布)の事実上の標準になっている。 API の世界では REST レスポンスの大半が JSON であり、e-Stat など公的統計 API も JSON を返す。 受け取ったネスト JSON を分析にかけるときは、上の「JSON→表」ボタンと同じことを pandas の pd.json_normalize(data, record_path="data", meta=...) が行う — ネストされたキーは metadata.source のようなドット記法の列名に展開され、DataFrame として CSV 的な平坦表に着地する。 「階層の JSON ↔ 平坦な表」を自在に往復できることが、構造化データを扱うデータエンジニアリングの基礎体力になる。
┌─────────────────────────┐
│ データ交換フォーマット │
└────────┬────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ CSV │ │ JSON │ │ XML │
│ (表形式) │ │ (階層) │ │ (汎用) │
└─────────┘ └────┬────┘ └─────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ JSON Schema │ │JSON Lines │ │ JSON-LD │
│ (型検証) │ │(1行1オブジェクト)│ │ (Linked Data)│
└─────────────┘ └─────────────┘ └──────────────┘
│
┌───────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌────────┐
│ REST API│ │ NoSQL │ │設定ファイル│
│(e-Stat等)│ │(MongoDB)│ │(package.json)│
└─────────┘ └─────────┘ └────────┘
バイナリ系派生:
- MessagePack (JSON 互換バイナリ)
- BSON (MongoDB 内部)
- Protobuf, Avro (スキーマ駆動)
JSON (JavaScript Object Notation) は Web API ・ 設定ファイル ・ NoSQL DB のデファクトデータ交換形式。 単独で意味を持たず、 解析ライブラリ・スキーマ検証・データベースと一体で使う。
json.loads / pandas.read_json / jq ・ JSON Schema による検証 ・ DataFrame 変換 (pd.json_normalize でネスト構造を表形式に展開) ・ NoSQL DB (MongoDB)JSON は階層構造を表現できる点が CSV より優位だが、 大量データには非効率 (テキスト + キー名重複)。 100MB を超えるなら Parquet (列指向 + 圧縮) を検討。
データ形式を、 構造・サイズ・用途で 3 段階で判定する。
初心者は import json; data = json.loads(text) で Python の dict/list に変換できる。 ネスト構造を表形式にしたいときは pd.json_normalize(data) が便利。
JSON(JavaScript Object Notation=データ交換のためのテキスト形式)は「値ひとつひとつに名札(キー)を貼って運ぶ」自己記述的なフォーマット。このページの他セクションが Schema・JSON Lines・API 取得を扱ったので、ここでは「表データを JSON にすると、書き方(orient)次第で大きさが激変する」という別角度に絞る。題材は SSDSE-B-2026 の 2023 年・47 都道府県・112 列(実測データ)。姉妹ページの CSV(ファイルサイズの見積り)や API(文字列型の検算)とは重ならない、「構造の冗長性」という JSON 固有の論点を扱う。
CSV は「見出し(列名)を先頭で 1 回だけ書き、あとは値だけ並べる」。対して JSON の records 形式は1 行ごとに列名という名札を貼り直す——付箋を全セルに貼るイメージ。だから階層構造や単一レコードには強いが、同じ列が何十行も続く「表」を運ぶと名札の重複でかさむ。逆に、ネストした JSON を pd.json_normalize() に通すと、階層のキーが meta.year・pref.code のようにドットで平坦化され、tidy な表(DataFrame / 整然データ)に落ちる。「名札を貼る/剥がす」の往復が JSON 運用の勘所。
to_csv=30,405 バイトに対し、to_json(orient="records")=75,578 バイト(CSV の 2.49 倍)。ところが列名を 1 回だけ持つ orient="split" にすると31,121 バイト(CSV の 1.02 倍)まで縮む。差の正体はキー名の重複——records は 112 個の列名を 47 行ぶん、計 5,264 回書く。JSON が「大きい」のは形式のせいではなく書き方の選択ミスであることが多い。NaN は「JSON もどき」を生む。 json.dumps({"x": float("nan")}) は {"x": NaN} を返すが、NaN は JSON 標準(RFC 8259)に存在しない。Python 同士なら読み戻せても、JavaScript の JSON.parse や厳格パーサーは構文エラーで落ちる。欠損は null に正規化する(pandas なら to_json が NaN→null に変換)。json.loads('{"a":1,"a":2}') は例外を出さず {"a": 2}——先の値が警告なく捨てられる。手組みの JSON や複数ソースのマージで起きやすく、キー順序も保証されない前提で書く。json_normalize で平坦化しても、値が "14086000" のようにクオート付きなら型は文字列のまま。+ は連結になり "14086000"+"14086000"="1408600014086000"。列を pd.to_numeric(..., errors="coerce") で数値化してから集計する(型の検算は API ページも参照)。下は「県数を増やすと CSV・JSON(split)・JSON(records) のバイト数がどう伸びるか」を、実測アンカー(1 県と 47 県)からの線形補間で見るミニ実験。バーをドラッグ(スマホは指でなぞる)か「🎲」で県数が変わる。47 県ちょうどが上記①の実測値。(補間途中の値は概算。両端=1 県・47 県のみ実測。)
教訓:表データを JSON で渡すなら orient="split"(列名を 1 回だけ持つ)や CSV/Parquet を選べば、records の名札重複を避けられる。