本文へスキップ
jsonbeautifiers
日本語

JSONの平坦化

ネストしたJSONを1階層のキーパスに変換し、元に戻すこともできます。

ネストあり
フラット

貼り付けたものがブラウザの外に出ることはありません。 connect-src の許可リストにより、これは約束ではなくブラウザによる保証になっています。 自分で確かめる

平坦化は、ネストしたドキュメントをドット区切りのパス1階層に変えます。{"a":{"b":1}}は{"a.b":1}になります。逆変換で元に戻せます。

JSONを何か「四角い」ものに読み込ませる前にやる処理です。表計算、データフレーム、フィーチャーフラグのストア、環境変数ファイル、フォームなど。

往復と、それが壊れる唯一のケース

平坦化してから元に戻すと、元のドキュメントがそのまま返ってきます。多くの実装が黙って捨ててしまう空のオブジェクトや空の配列も含めてです。

戻せないケースはひとつだけあります。キー自身に区切り文字が含まれている場合です。{"a.b": 1}を平坦化したパス"a.b"は、ネストした{"a":{"b":1}}と区別がつきません。このツールはそれを検出して警告します。戻せないものを黙って作ることはしません。その場合は別の区切り文字を選んでください。

配列:インデックス記法か角括弧記法か

ドット記法ではtags.0、tags.1になります。角括弧記法ではtags[0]、tags[1]です。どちらもここでは往復でき、入力側のパーサーは両方を受け付けます。

ドット記法はpandasのjson_normalizeが出力する形式で、多くのCSVパイプラインが前提にしている形式でもあります。角括弧記法は、キーが数値になり得る場面で読みやすくなります。tags[0]とtags.0は曖昧ですが、tags["0"]はそうではないからです。

平坦化で情報が失われる場所

数値のオブジェクトキーは、平坦化するとインデックスと区別がつかなくなります。{"2024": {"total": 1}}は"2024.total"になり、配列検出を有効にしたまま戻すと、空きが2024個ある配列ができてしまいます。

キーが本当に数値の文字列である場合は、「数値キーを配列とみなす」をオフにしてください。年、HTTPステータスコード、IDをキーにしたデータではよくあることです。

How to do this in code

コードでの平坦化。

py Python、pandas

record_pathは、1対多の関係を番号付きの列ではなく行として展開するための引数です。

import pandas as pd

# The workhorse. sep defaults to '.'
df = pd.json_normalize(records)

# Explode a nested array into one row per element
df = pd.json_normalize(
    records,
    record_path='items',
    meta=['id', 'created_at'],
)
sh jq
# Every leaf as a dotted path
jq -r 'paths(scalars) as $p | "\($p | join(".")) = \(getpath($p))"' in.json

# A flat object rather than lines
jq '[leaf_paths as $p | {(($p | map(tostring) | join("."))): getpath($p)}] | add' in.json
js JavaScript

空のコンテナを扱う分岐こそ、多くの実装が書き落としている行であり、それらが往復できない理由です。

function flatten(value, prefix = '', out = {}) {
  if (value && typeof value === 'object') {
    const entries = Array.isArray(value)
      ? value.map((v, i) => [i, v])
      : Object.entries(value);
    if (entries.length === 0) {
      out[prefix] = value;      // preserve {} and []
      return out;
    }
    for (const [k, v] of entries) {
      flatten(v, prefix ? `${prefix}.${k}` : String(k), out);
    }
    return out;
  }
  out[prefix] = value;
  return out;
}

よくある質問

どの区切り文字を使うべきですか?
キーにドットが含まれていないかぎり、ドットです。次点は一般にアンダースコアで、結果がすでにパスとして扱われる場所に渡るならスラッシュが便利です。
ドキュメントの一部だけを平坦化できますか?
深さの上限を設定してください。それより深い部分はネストした値のまま残ります。深い部分が、問い合わせではなく保存するだけの不透明な塊であるときにちょうどよい挙動です。
nullはどうなりますか?
既定では、値がnullのフラットなキーとして保持されます。nullを省くオプションもありますが、差分を取るときには便利で、往復させるときには危険です。