JSON から Excel へ:四つの方法と、それぞれがどこで忠実度を失うか
要点:ネストした構造、不揃いなフィールド、配列の配列が、JSON から表計算への変換の破綻点です。結果までの時間順に四つの方法を並べ、それぞれのトレードオフを示します。

要点:平坦なオブジェクト配列や配列を包むオブジェクトはオンラインツールが最速です。毎週更新されるデータなら Power Query、清掃するフィールドが多いなら pandas、変換をパイプラインに組み込む必要があるときだけ Node スクリプトを使います。ただしネストしたデータや不揃いなキーに触れる前に「1 行」の意味を決めてください。
変換自体は難しくありません。難しいのは、後から数値がテキストになり、いくつかのネスト階層が一つに潰れていたことに気づくことです。方法を選ぶ前にデータ構造を読み、手戻りを避けましょう。
1. まず、どの種類の JSON かを把握する
ファイルを開き、最外層を見ます。
| 構造 | 形 | 変換の難しさ |
|---|---|---|
| 平坦なオブジェクト配列 | [{"a":1,"b":2},{...}] |
最も簡単。1 レコード 1 行 |
| 配列を包むオブジェクト | {"data":[{...}],"total":100} |
簡単。data の階層を取る |
| ネストしたオブジェクト | {"user":{"name":"x"}} |
中程度。平坦化するか文字列に結合するかを決める |
| 配列の配列 | {"tags":["a","b"]} |
面倒。1 行に収まらず、テーブル分割が必要 |
| 不揃いなキー | レコードごとにフィールドが異なる | 面倒。列の大半が空になる |
最初の二つは成功率ほぼ 100% で直接変換できます。後の三つは「1 行」の意味を先に決めなければ、できあがったシートは使えません。
2. 方法 1:オンラインツール(1 分)
JSON to tableを開き、データを貼り、区切り文字とヘッダーを選び、CSV をエクスポートし、Excel で開きます。数十から数千行の整ったデータに適します。
必ず注意すべき三点:
- 得られるのは CSV で、xlsx ではありません。 Excel が CSV を開くとシステムロケールからエンコーディングを推測するので、日本語が文字化けすることがあります。「データ → テキスト/CSV から」を使い、UTF-8 を明示的に選ぶ方が信頼できます。
0で始まる識別子はゼロを失います。"007"は CSV ではテキストですが、Excel が自動的に数値 7 に変換します。インポート時にその列をテキストに設定するか、JSON 側で接頭辞を付けます。- 長い数値が指数表記になります。 15 桁を超えるマイナンバーや注文番号は精度を失います。そのようなフィールドは JSON ですでに文字列であるべきで、インポート後もテキスト形式を保たなければなりません。
3. 方法 2:Excel 内蔵の Power Query(再利用可能)
データが毎週更新されるなら、Excel 内蔵の Power Query を使います。一度設定すれば、あとは「更新」を押すだけです。
- 「データ」→「データの取得」→「ファイルから」→「JSON から」。
- ファイルを選ぶと Power Query エディタに入り、
List/Record構造が見えます。 - 列ヘッダーの右側の展開アイコンをクリックし、ネストしたフィールドを一段ずつ展開します。
- 列名と型が正しくなったら「閉じて読み込む」。
利点は、ネストを層ごとに複数の列へ展開でき、型を手動で割り当てられること。代償は、最初の設定で List と Record の違いを理解する必要があることです。List は配列、Record はオブジェクトで、展開の方向が異なります。
4. 方法 3:Python + pandas(フィールドが多く、清掃が必要)
import pandas as pd
# 配列を包むオブジェクト:まずリストの階層に到達する
rows = pd.read_json('data.json')['data']
# ネストしたフィールドを複数の列に展開する
frame = pd.json_normalize(rows, sep='_')
frame.to_excel('out.xlsx', index=False)
json_normalize の鍵となるパラメータは sep で、ネストしたフィールドを展開した後の列命名スタイルを決めます。user_name は Excel で user.name より扱いやすいです。配列の配列(tags など)には追加の explode が必要です。
frame = frame.explode('tags')
この手順は 1 行を複数行に分割し、一対多の関係を保つ唯一の方法です。代償は残りのフィールドが重複することです。
5. 方法 4:Node スクリプト(パイプラインに組み込む必要があるとき)
import { readFileSync, writeFileSync } from 'node:fs';
const rows = JSON.parse(readFileSync('data.json', 'utf8'));
const headers = [...new Set(rows.flatMap((row) => Object.keys(row)))];
const escapeCell = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
const lines = [
headers.join(','),
...rows.map((row) => headers.map((key) => escapeCell(row[key])).join(',')),
];
writeFileSync('out.csv', '\uFEFF' + lines.join('\n'), 'utf8');
省略すべきでない二つの詳細:先頭に \uFEFF(BOM)を書いて Excel が UTF-8 を認識できるようにし、セル内の二重引用符を "" にエスケープして列がずれないようにします。
6. 変換が必ず失う三つのもの
| 失われるもの | 症状 | 対処 |
|---|---|---|
| 型情報 | 数値と真偽値がテキストや TRUE/FALSE になる |
インポート後に列の型を手動設定するか、Power Query で指定する |
| ネスト階層 | 深いフィールドが [object Object] に潰れる |
json_normalize か一段ずつの展開を使う。String(obj) は決して使わない |
| 一対多の関係 | 配列が一つのセルに詰め込まれる | explode で行を分割するか、別の関連テーブルを作る |
また、null と「フィールドなし」はシートでは同じに見えます(どちらも空セル)が、業務上は異なる意味を持ちます。区別が必要なときは、JSON で null を明示的に文字列 "null" に変換するか、_has_field のようなマーカー列を足します。
7. 変換後の確認:先頭行だけを見ない
変換後、少なくとも三つをスポットチェックします。
- レコード数:CSV の行数は配列長 + 1(ヘッダー)と等しいはずです。行が足りない場合は末尾の改行が飲み込まれたのが定番です。
- null 率が最も高い列:空セルが多いのはキー名の不揃いを示します。元の JSON が二つの綴り(
userIdとuser_id)を使っていないか確認してください。 - 最も長い行:ネストした配列がたいていそこに落ちます。切り詰められていないことを確認します。
逆方向(Excel/CSV から JSON)が必要なときは CSV to JSONを使い、ヘッダーがキー名として扱われることを覚えておいてください。ヘッダーを変えることはフィールド名を変えることです。
FAQ
オンラインツール、Power Query、pandas はどう選べばよいですか?
一度だけ変換する数十から数千行の整ったデータにはオンラインツールを使います。毎週更新されるデータには Power Query を使います。一度設定し、ワンクリックで更新できるからです。清掃するフィールドが多いなら pandas の json_normalize、変換をパイプラインに組み込む必要があるなら Node スクリプトを使います。
CSV で日本語が文字化けするのはなぜですか?
エクスポートが xlsx ではなく CSV で、Excel がシステムロケールからエンコーディングを推測するため、BOM なしの UTF-8 は文字化けします。ファイル先頭に \uFEFF の BOM を書くか、「データ → テキスト/CSV から」で開き UTF-8 を明示的に選んでください。
ネストしたオブジェクトは平坦化すべきか、追加の行に分割すべきか?
文字列として貼り付けるのではなく、層ごとに平坦化してください。Power Query はネストしたフィールドを列に展開し、pandas では sep 付きの json_normalize が同じことをします。配列の配列には 1 行を複数行に分割する追加の explode が必要です。深いオブジェクトを String(obj) に渡すと [object Object] しか得られません。