JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

【JavaScript】ドット記法でフラット化されたオブジェクトを、ネストされたオブジェクトと配列へ展開する方法

はじめに

APIレスポンスやフォームデータなどでは、"car.make" のようにドット区切りのキーを持つフラットなオブジェクトが扱われることがあります。本記事では、こうしたドット記法のオブジェクトを、ネストされたオブジェクトや配列を含む通常の構造へと展開(アンフラット化)する方法を解説します。

まず、次のようなオブジェクトを例として考えます。

const obj = {
  "firstName": "John",
  "lastName": "Green",
  "car.make": "Honda",
  "car.model": "Civic",
  "car.revisions.0.miles": 10150,
  "car.revisions.0.code": "REV01",
  "car.revisions.0.changes": "",
  "car.revisions.1.miles": 20021,
  "car.revisions.1.code": "REV02",
  "car.revisions.1.changes.0.type": "asthetic",
  "car.revisions.1.changes.0.desc": "Left tire cap",
  "car.revisions.1.changes.1.type": "mechanic",
  "car.revisions.1.changes.1.desc": "Engine pressure regulator",
  "visits.0.date": "2015-01-01",
  "visits.0.dealer": "DEAL-001",
  "visits.1.date": "2015-03-01",
  "visits.1.dealer": "DEAL-002"
};

このようなオブジェクトを受け取り、ネストされたオブジェクトと配列へと変換するJavaScript関数を実装します。期待される出力は以下のとおりです。

const output = {
  firstName: 'John',
  lastName: 'Green',
  car: {
    make: 'Honda',
    model: 'Civic',
    revisions: [
      { miles: 10150, code: 'REV01', changes: '' },
      { miles: 20021, code: 'REV02', changes: [
        { type: 'asthetic', desc: 'Left tire cap' },
        { type: 'mechanic', desc: 'Engine pressure regulator' }
      ]}
    ]
  },
  visits: [
    { date: '2015-01-01', dealer: 'DEAL-001' },
    { date: '2015-03-01', dealer: 'DEAL-002' }
  ]
};

実装のポイント

基本的なアプローチはシンプルです。各キーを split('.')' で分割してパスのセグメントを取得し、そのパスを順番にたどりながら結果オブジェクトを組み立てていきます。ここで重要になるのが、次のセグメントが数値(配列のインデックス)かどうかを isFinite() で判定する点です。数値であれば配列を、そうでなければオブジェクトを生成することで、自動的に適切な構造が作られます。

コード例

const obj = {
  "firstName": "John",
  "lastName": "Green",
  "car.make": "Honda",
  "car.model": "Civic",
  "car.revisions.0.miles": 10150,
  "car.revisions.0.code": "REV01",
  "car.revisions.0.changes": "",
  "car.revisions.1.miles": 20021,
  "car.revisions.1.code": "REV02",
  "car.revisions.1.changes.0.type": "asthetic",
  "car.revisions.1.changes.0.desc": "Left tire cap",
  "car.revisions.1.changes.1.type": "mechanic",
  "car.revisions.1.changes.1.desc": "Engine pressure regulator",
  "visits.0.date": "2015-01-01",
  "visits.0.dealer": "DEAL-001",
  "visits.1.date": "2015-03-01",
  "visits.1.dealer": "DEAL-002"
};

const unflatten = (obj = {}) => {
  const result = {};
  let temp, substrings, property, i;
  for (property in obj) {
    // キーを「.」で分割してパスを取得
    substrings = property.split('.');
    temp = result;
    // 最後のセグメント以外をたどりながら中間ノードを生成
    for (i = 0; i < substrings.length - 1; i++) {
      if (!(substrings[i] in temp)) {
        if (isFinite(substrings[i + 1])) {
          // 次が数値なら配列を作成
          temp[substrings[i]] = [];
        } else {
          // それ以外はオブジェクトを作成
          temp[substrings[i]] = {};
        }
      }
      temp = temp[substrings[i]]; // 階層を一段深く進む
    }
    // 最後のセグメントに値を代入
    temp[substrings[substrings.length - 1]] = obj[property];
  }
  return result;
};

console.log(JSON.stringify(unflatten(obj), undefined, 4));

出力結果

コンソールには次のように出力されます。

{
  "firstName": "John",
  "lastName": "Green",
  "car": {
    "make": "Honda",
    "model": "Civic",
    "revisions": [
      {
        "miles": 10150,
        "code": "REV01",
        "changes": ""
      },
      {
        "miles": 20021,
        "code": "REV02",
        "changes": [
          {
            "type": "asthetic",
            "desc": "Left tire cap"
          },
          {
            "type": "mechanic",
            "desc": "Engine pressure regulator"
          }
        ]
      }
    ]
  },
  "visits": [
    {
      "date": "2015-01-01",
      "dealer": "DEAL-001"
    },
    {
      "date": "2015-03-01",
      "dealer": "DEAL-002"
    }
  ]
}

処理の流れを整理すると

  1. キーの分割: 各プロパティ名を split('.')' で分割します。例えば "car.revisions.0.miles"["car", "revisions", "0", "miles"] になります。
  2. 中間ノードの生成: 最後のセグメント以外の位置にコンテナが存在しない場合、直後のセグメントが数値なら配列、文字列ならオブジェクトを新しく作成します。
  3. 値の代入: パスの最終地点に、元のオブジェクトの値を代入します。

注意点

この実装はシンプルで多くの場面で十分機能しますが、キー自体に「.」を含むプロパティが混在しているケースや、非常に深いネスト構造を扱う場合は挙動に注意が必要です。実務でより堅牢な処理が求められる場合は、lodash の _.set() のような実績のあるユーティリティライブラリを活用するのも有効な選択肢です。

  1. JavaScriptでサブオブジェクトや配列を含むオブジェクトをインポートする方法

    はじめに JavaScriptでは、ESモジュール(import / export)の仕組みを利用することで、別ファイルに定義したオブジェクトを簡単に読み込めます。オブジェクトの中にサブオブジェクト(ネストされたオブジェクト)や配列が含まれていても、そのままインポートして扱うことができます。以下に具体的なコード例を示します。 コード例 index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta na

  2. 【JavaScript入門】複数のオブジェクトを1つにマージして単一のオブジェクト配列を作成する方法

    JavaScriptでは、Object.assign()メソッドとスプレッド構文(...)を組み合わせることで、配列内の複数のオブジェクトを1つのオブジェクトに簡単にマージすることができます。 Object.assign()は、1つ以上のコピー元オブジェクトから、コピー先のオブジェクトへすべてのプロパティをコピーするためのメソッドです。スプレッド構文を使って配列の要素を展開することで、各オブジェクトのキーと値が1つのオブジェクトに統合されます。 コード例 以下のコードは、配列内の複数のオブジェクトを単一のオブジェクトにマージする例です。 <!DOCTYPE html> <ht