FormatArc の JSON to CSV ツール。左にネストした address を含む JSON 配列、右に address.city 列を含む CSV が表示されているFormatArc の JSON to CSV ツール。左にネストした address を含む JSON 配列、右に address.city 列を含む CSV が表示されている
公開日: 2026-07-26

JSON CSV 変換 — ネスト・配列・null の出力差を 4 ツールで実測

TL;DR — 用途別 10 秒早見表

  • 今すぐ表にしたい: FormatArc JSON to CSV。ブラウザ内で完結しアップロードなし、ネストしたオブジェクトはドット記法の列になります
  • 1 レコードの中にオブジェクトの配列がある (注文と明細など): まず「1 行 = 注文」か「1 行 = 明細」かを決めます。JSON の形別の対処
  • 配列の要素ごとに 1 行を作りたい: mlr --ijson --ocsv cat (Miller) か pandas.json_normalize(data, record_path=...)
  • ID が 16 桁以上ある: JavaScript 製のコンバータを使ってはいけません。2^53 を超える整数を参照
  • Excel で開く予定がある: ダブルクリックする前に表計算ソフトで壊さずに開くを読んでください
方法セットアップネストしたオブジェクトレコード内の配列ブラウザ完結 (アップロードなし)
FormatArc不要ドット記法の列JSON 文字列として 1 セルに保持あり
json-2-csv (npm)npm i json-2-csvドット記法の列JSON 文字列として 1 セルに保持なし (Node)
Miller (mlr)brew install millerドット記法の列items.1.sku items.2.sku … と列展開なし (CLI)
pandas json_normalizepip install pandasドット記法の列Python の repr を 1 セル、record_path で行展開なし (Python)
jqbrew install jq自分でマッピングを書く自分でマッピングを書くなし (CLI)

この表の内容はすべて実測値です。入力データ・測定スクリプト・生の出力はリポジトリの scripts/benchmarks/json-to-csv-guide/ に置いてあり (2026-07-26 実測)、以下に引用する出力はすべてその実行結果そのままです。

30 秒で変換する

JSON to CSV を開き、オブジェクトの配列を貼り付けて実行を押します。この入力:

[
  { "name": "Mika", "email": "mika@example.com", "role": "admin", "address": { "city": "Tokyo" } },
  { "name": "Noah", "email": "noah@example.com", "role": "viewer", "address": { "city": "Osaka" } }
]

に対する出力:

name,email,role,address.city
Mika,mika@example.com,admin,Tokyo
Noah,noah@example.com,viewer,Osaka

FormatArc の JSON to CSV でネストした JSON 配列を address.city 列付きの CSV に変換した画面FormatArc の JSON to CSV でネストした JSON 配列を address.city 列付きの CSV に変換した画面

変換はページの中で完結します。JSON がサーバーに送られることはありません。本番 API のレスポンスをそのまま貼り付ける場面ではこの違いが効いてきます。何が守られて何が守られないのかはオンライン変換ツールは安全かで整理しています。

入力が受け付けられなかった場合は、JSON Formatter と同じ経路で行番号付きのエラーが返ります。原因の切り分けは JSON parse error の直し方を参照してください。

同じ JSON を 4 つのコンバータに通したら、答えが割れた

JSON は木構造、CSV は長方形です。どのコンバータも木を長方形に潰すためのルールを独自に決めており、そのルールはツールごとに違います。15 個の入力を 4 つのコンバータに通し、それぞれの出力をそのまま記録しました。

実行環境 (results.json より): macOS arm64 上の Node v26.3.1、FormatArc は lib/tooling.ts (PapaParse 5.5.2)、json-2-csv 5.5.11、Miller 6.19.0、pandas 3.0.5。4 者ともオプション指定なしのデフォルト設定です。何も設定しなかったときに何が出てくるか、が知りたい情報だからです。

4 者が一致した部分

ネストしたオブジェクトはドット記法に平坦化されます。{"name":"Mika","address":{"city":"Tokyo","zip":"150-0001"}} に対して 4 者とも:

name,address.city,address.zip
Mika,Tokyo,150-0001

3 階層でも同じ (meta.created.by.name)。配列で包まれていない単一オブジェクトは、4 者とも 1 行の CSV になります。カンマ・ダブルクォート・改行を含む値のクォートも一致しました。

who,quote,note
"Smith, John","She said ""hi""","line1
line2"

これは RFC 4180別タブで開きます 2 章のクォート規則と一致します。改行・ダブルクォート・カンマを含むフィールドはダブルクォートで囲み、内側のダブルクォートは 2 つ重ねてエスケープする、というものです。ファイルを diff する人向けに 1 点だけ補足すると、レコード区切りは FormatArc が CRLF (PapaParse のデフォルトで、RFC 4180 が規定しているのもこちら)、json-2-csv / Miller / pandas は LF でした。

配列は 3 通りに割れる

入力:

[{"name":"Mika","tags":["admin","billing"]},{"name":"Noah","tags":["viewer"]}]
コンバータ出力
FormatArcname,tags / Mika,"[""admin"",""billing""]" / Noah,"[""viewer""]"
json-2-csvFormatArc と同一
Millername,tags.1,tags.2 / Mika,admin,billing / Noah,viewer,
pandasname,tags / Mika,"['admin', 'billing']" / Noah,['viewer']

まったく別の 3 種類の成果物です。FormatArc と json-2-csv は配列を「有効な JSON テキスト」として 1 セルに保持するので、後からセル単位でパースし直せます。Miller は表を横に広げます。便利ですが、あるレコードのタグが 40 個あれば列数が 40 に膨らみます。pandas はシングルクォートの Python repr を書き出すため、これは JSON として不正で、後段で JSON.parse すると失敗します。

オブジェクトの配列も同様です。{"order":"A-1","items":[{"sku":"X1","qty":2},{"sku":"X2","qty":1}]} は FormatArc と json-2-csv では 1 セルの JSON テキストに、Miller では items.1.sku, items.1.qty, items.2.sku, items.2.qty になります。

キーの欠落: 空セルか、"undefined" という文字列か、エラーか

実際の API レスポンスにはオプショナルなフィールドが含まれます。入力:

[{"id":1,"name":"Mika"},{"id":2,"name":"Noah","nickname":"No"},{"id":3,"phone":"03-0000-0000"}]

FormatArc と pandas は空セルにします。

id,name,nickname,phone
1,Mika,,
2,Noah,No,
3,,,03-0000-0000

json-2-csv は欠落部分に undefined という文字列を書き込みます。

id,name,nickname,phone
1,Mika,undefined,undefined
2,Noah,No,undefined
3,undefined,undefined,03-0000-0000

Miller はファイル自体を拒否します: mlr: CSV schema change: first keys "id,name"; current keys "id,phone"。ストリーミング処理系としては妥当な設計ですが、オプショナルなフィールドが 1 つ増えただけで昨日まで動いていたパイプラインが止まる、ということでもあります。

FormatArc は全レコードのキーの和集合を初出順で取り、1 行目を書き出す前に列数を確定させます。行がずれることはありません。

null は空セルか、n-u-l-l という 4 文字か

[{"id":1,"deleted_at":null},{"id":2,"deleted_at":"2026-07-01"}]

FormatArc と pandas は空セル、json-2-csv と Miller は null という文字列を書きます。この CSV をそのままデータベースに取り込むと、前者は本物の NULL に、後者は NULL に見える 4 文字の文字列になります。JSON から CSV を経由したあとで「WHERE 句が一致しない」と悩む原因のほとんどがこれです。

空オブジェクトからは 4 種類の表ができる

[{"id":1,"meta":{}},{"id":2,"meta":{"source":"api"}}] の場合:

コンバータ出力
FormatArcid,meta,meta.source — 空の {} のせいで、全行が空の meta 列が残る
json-2-csvヘッダーは同じだが {}{"source":"api"} をそのままセルに書き、値が重複する
Millerエラー: CSV schema change: first keys "id,meta"; current keys "id,meta.source"
pandasmeta 列ごと消える: id,meta.source

FormatArc の挙動は正直ですが見栄えはよくありません。使われていない空の列が 1 本増えたら、ペイロードのどこかに空オブジェクトがあるということです。

キー名にドットが入ると、3 ツールが列を上書きする

これはデータが消えるケースで、4 者のうち 3 者が取りこぼします。入力:

[{"a.b":1,"a":{"b":2}}]

このレコードには別々の値が 2 つあります。文字どおり a.b という名前のキーが持つ 1 と、ネストされた ab のパスが持つ 2 です。FormatArc・Miller・pandas はいずれも次を出力します。

a.b
2

1 が消えています。区別できたのは json-2-csv だけで、文字どおりのキーの側をエスケープします。

a\.b,a.b
1,2

page.view.count のような分析イベント名、MongoDB 系のドキュメント、Prometheus 風のラベルなど、ドットを含むキー名を使っている JSON では、変換前に衝突がないか確認するか、json-2-csv を使ってください。

2^53 を超える整数は JavaScript 系で丸められる

[{"id":9007199254740993,"order_no":12345678901234567890}]
コンバータ出力
FormatArc9007199254740992,12345678901234567000
json-2-csv9007199254740992,12345678901234567000
Miller9007199254740993,12345678901234567890
pandas9007199254740993,12345678901234567890

これは CSV の問題でも、2 つの JavaScript 製ツールのバグでもありません。JSON.parse はすべての数値を倍精度浮動小数点数にするため、2^53 より大きい整数をすべて正確には表現できません。値は CSV ライターに渡る前の時点ですでに壊れています。Snowflake ID、X (Twitter) の ID、一部の決済参照番号、64bit のデータベースキーはこの範囲に入ります。桁の長い ID を扱うなら、JSON 側で文字列としてクォートしておくか、桁を保持する Miller / pandas / jq を使ってください。

壊れた入力のとき: 明示的なエラーか、無言の空ファイルか

入力FormatArcjson-2-csvMillerpandas
["a","b","c"]エラー「配列の各要素はオブジェクトである必要があります」空行 3 行、エラーなし例外例外
[]エラー「JSON 配列が空です」空行 1 行、エラーなし空出力、エラーなし空出力、エラーなし

3 つのうち最悪なのは「終了コード 0 で空出力」です。cron ジョブが昨日の正常なファイルを、何事もなかったかのように空ファイルで上書きします。

FormatArc の変換ルール (実装そのまま)

以下は要約ではなく実装のルールです。lib/tooling.tsconvertJsonToCsv に対応し、上の実測結果とも一致します。

  1. トップレベルはオブジェクトの配列、または単一オブジェクト。単一オブジェクトは 1 行の CSV になります。プリミティブの配列・空配列・裸の文字列や数値はメッセージ付きで拒否されます
  2. ネストしたオブジェクトは再帰的にドット記法の列へ平坦化されます (address.city / meta.created.by.name)。深さの制限はありません
  3. 配列は展開しません。JSON.stringify で文字列化して 1 セルに保持するので、["admin","billing"] はパース可能なまま残ります
  4. 列は全レコードのキーの和集合を初出順に並べたものです。最後のレコードにしか出てこないキーにも列が割り当てられ、それ以前の行は空セルになります。行がずれることはありません
  5. nullundefined は空セル。空オブジェクト {} も空セルになり、自分自身の列を持ちます
  6. クォートは PapaParse の unparse に従います。カンマ・クォート・改行を含むフィールドはクォートされ、内側のクォートは 2 重化、レコード区切りは CRLF です
  7. 不正な JSON は行番号付きで報告されます (JSON Formatter と同じエラー経路)

データはタブの外に出ません。アップロードの工程もサーバー上の一時ファイルもなく、すでに読み込んだページの中で関数が 1 回呼ばれるだけです。

JSON の形別: 何をどう変換するか

フラットなオブジェクトの配列

判断するものはありません。貼り付けて実行するだけです。

ネストしたオブジェクトを含むレコード

ドット記法はどのツールでも既定の答えで、人間の目でも元の場所をたどれます (address.city がどこから来たかは自明)。事前に確認すべきなのは 2 点だけです。すでにドットを含むキー名がないか (前述の衝突)、そして CSV の受け手がヘッダーのドットを扱えるか。SQL のローダーによってはヘッダーのクォートが必要ですが、Google スプレッドシートはそのまま読めます。

スカラーの配列を含むレコード

3 択です。

  • JSON テキストのまま 1 セルに保持する (FormatArc のデフォルト)。CSV が中間ファイルで、後段のスクリプトが読み直す場合に向きます
  • Miller で tags.1 tags.2 に列展開する。要素数が小さく固定の場合 (座標のペア、RGB の 3 要素) に向きます
  • 人間が読む列なら、変換前に区切り文字で連結してしまう: jq '.[] |= (.tags |= join(";"))' data.json を通してから変換します。セルがクォートを必要としないよう、区切りは , ではなく ; にしておきます

オブジェクトの配列を含むレコード (いちばん難しい形)

コンバータに触る前に、「CSV の 1 行が何を表すのか」を決めます。

  • 1 行 = 親 (注文 1 件): 配列は JSON テキストのまま 1 セルに置きます。FormatArc のデフォルトがこれです。明細が壊れずに残り、後からスクリプトでセルをパースできます
  • 1 行 = 子 (明細 1 件): これは形式の違いではなく別のテーブルです。pandas.json_normalize(data, record_path="items", meta=["order"]) を使うと、親のフィールドを子の各行に繰り返して展開できます
  • ファイルを 2 つに分ける: 親フィールドを orders.csv に、子を jq '[.[] | .order as $o | .items[] | {order:$o} + .]' data.json で取り出して items.csv にします。正規化された答えで、CSV をデータベースに入れるならこれを選びます

避けるべきなのは、可変長の配列を Miller のデフォルトで items.1.sku, items.2.sku, … と横に広げることです。列数が「たまたま子要素が最多だったレコード」で決まるため、データが変わるたびにスキーマが変わります。

キーが揃っていないレコード

FormatArc と pandas は設定なしで処理します。Miller の場合は unsparsify を挟むと、中断せずに欠落を埋めてくれます。

mlr --ijson --ocsv unsparsify data.json

あるいは jq '[.[] | {id, name, nickname, phone}]' で先に全レコードへ全キーを持たせても構いません。

Excel と Google スプレッドシートで壊さずに開く

データを壊すのはたいていコンバータではなく、表計算ソフトの側です。

  • 文字コード。CSV には文字コードを書く場所がありません。RFC 4180別タブで開きます も、US-ASCII 以外の文字集合は MIME の charset パラメータと併用する、と書いているだけで、ローカルファイルはそのパラメータを持ちません。FormatArc のダウンロードは BOM なしの UTF-8 です。Windows の Excel は BOM がないと非 ASCII 文字を Windows-1252 として読むことがあるため、ダブルクリックせず「データ」タブの「テキストまたは CSV から」で UTF-8 を指定して取り込みます。Google スプレッドシートは UTF-8 を正しく判定します
  • 16 桁以上の数値。Excel の仕様には数値の精度が 15 桁別タブで開きますと明記されており、それを超える桁はゼロに置き換えられます。前述の 2^53 の丸めと合わせると、長い ID は二重に壊れる可能性があります。該当する列は文字列として取り込んでください
  • 先頭のゼロ。00703-0000-0000 は、列を文字列として取り込まない限り 7 や日付になります
  • 日付に見える文字列。2026-07-01 はもちろん、1-2 のようなバージョン番号や一部の遺伝子名も自動変換されます。取り込み時に列の型を文字列に指定するのが確実です
  • = + - @ で始まるセル。今回の 4 者はいずれもエスケープしませんでした (=1+1 はそのまま出力されます)。評価するかどうかを決めるのは表計算ソフト側で、これが CSV インジェクション別タブで開きますの基本形です。ユーザー入力を含む CSV を他人が開く場合は、該当セルの先頭にシングルクォートを付けるか、列を文字列として取り込みます

ブラウザが向かない場合

curl の結果を貼り付けて確認するならブラウザが最速です。一方、2 GB のエクスポートや夜間バッチの 1 工程には向きません。

# Miller: オブジェクトを平坦化し、配列は添字付きの列に展開、ストリーミング処理
mlr --ijson --ocsv cat data.json > out.csv

# jq: マッピングを自分で書くので、推測される部分がない
jq -r '(.[0] | keys_unsorted), (.[] | [.[]]) | @csv' data.json > out.csv

# pandas: ドット記法の平坦化に加え、record_path で配列要素ごとの行展開ができる
python3 -c "import pandas,json; print(pandas.json_normalize(json.load(open('data.json'))).to_csv(index=False))" > out.csv

この 3 つは互換ではありません。json_normalize は辞書をドット記法の列に展開しますが、配列要素ごとに行を作るには record_path の指定が必要です。jq@csv は入力があらかじめスカラーの配列に整形されている必要があり、ネストしたオブジェクトを渡すと平坦化せずエラーになります。Miller はオブジェクトも配列もデフォルトで平坦化し (配列の添字は 1 始まり)、レコードのスキーマが揃っていないと前述のとおり停止します。詳細は Miller の flatten ドキュメント別タブで開きますjq マニュアル別タブで開きますpandas.json_normalize のリファレンス別タブで開きますを参照してください。

逆方向の変換と、そちら側の落とし穴 (型推論・文字コード・NDJSON 出力) は CSV JSON 変換ガイドにまとめてあります。

CSV から JSON に戻しても元には戻らない

往復しても元のドキュメントには戻りません。最初の例で作った CSV を CSV to JSON に通すと、次のようになります。

[
  {
    "name": "Mika",
    "email": "mika@example.com",
    "role": "admin",
    "address.city": "Tokyo"
  }
]

address.city はネストした address オブジェクトではなく、ドットを含む名前のフラットなキーとして戻ってきます。ドット記法を自動で再ネストするツールがないのは、address.city というキー名それ自体も正当な JSON であり、どちらのつもりだったのかコンバータには判別できないからです。前述のケース C13 で計測した衝突と同じ話です。

JSON から CSV への変換は「表計算・分析用の一方向のエクスポート」と割り切り、正本は JSON 側に置いてください。ネストした形に戻す必要がある場合は、たとえば jq 'map(reduce (to_entries[]) as {$key,$value} ({}; setpath($key | split("."); $value)))' のように意図的に再ネストし、結果を必ず確認します。

よくある質問

CSV が 1 列しかなく、中に JSON が入っているのはなぜですか

トップレベルが {"data":[...]} のような「配列を 1 つだけ持つオブジェクト」だったためです。先に配列を取り出してください (data の値だけを貼り付ける、または jq '.data' response.json を通す)。FormatArc は外側のオブジェクトを data という列に平坦化するだけで、中の配列を行として扱うことはありません。

ネストした配列の要素ごとに 1 行を作れますか

ブラウザ版ではできません。これは意図的な制約です。行数がデータ依存になり、親のフィールドが黙って複製されるためです。前述のとおり pandas.json_normalize(data, record_path="items", meta=[...]) を使うか、jq で先に整形してください。

列の中身が [{"sku":"X1"...}] になっています

オブジェクトの配列を JSON テキストとして 1 セルに保持した結果です。意図的な挙動で、後から元に戻せます。変更したい場合の 3 択はオブジェクトの配列を含むレコードにまとめました。

数値が変わってしまうことはありますか

JavaScript 自体の仕様の範囲でだけ起こります。ケース C14 で計測したとおり、2^53 を超える整数はパース時に丸められます。文字列は変更されません。ID が 16 桁以上あるなら、JSON 側で文字列としてクォートするか、JavaScript 以外のツールを使ってください。

データはどこかにアップロードされますか

されません。変換はページの中で実行され、JSON はタブの外に出ません。それを運ぶリクエスト自体が存在しないからです。理由と、ネットワークパネルで自分で確認する方法はオンライン変換ツールは安全かで説明しています。

JSON が壊れているとどうなりますか

壊れた CSV ではなく、行番号付きのメッセージが返ります。末尾カンマやシングルクォートなど典型的な原因は JSON parse error の直し方に、整形して読みやすく保つ方法は JSON 整形のコツにまとめてあります。

正本はどちらの形式で持つべきですか

構造を保持できるほうです。CSV は型のない長方形で、ネスト・配列・null"" の区別はすべて失われます。JSON を正本とし、CSV はその 1 つのビューとして扱ってください。形式そのものの制約は CSV とはで詳しく解説しています。