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

JavaScriptで2つの異なるオブジェクト配列をマージする方法【サンプルコード付き】

フロントエンド開発では、APIなどから取得した複数の配列を、共通のキーを基準に1つのデータ構造へまとめたい場面がよくあります。本記事では、JavaScriptを使って「質問の回答情報」と「選択肢情報」という2つの異なるオブジェクト配列を、ユーザーごとにグループ化してマージする方法を解説します。

マージ対象となる2つの配列

まず、ある人々が回答した質問に関する情報を含む、2つの異なるオブジェクト配列を用意します。

  • arr1: 各ユーザーの個人ID・質問番号・回答値を持つ配列
  • arr2: 質問番号・選択肢ID・選択値(色)を持つ配列
const arr1 = [
  { PersonalID: '11', questionNumber: '1', value: 'Something' },
  { PersonalID: '12', questionNumber: '2', value: 'whatever' },
  { PersonalID: '13', questionNumber: '3', value: 'anything' },
  { PersonalID: '14', questionNumber: '4', value: 'null' }
];

const arr2 = [
  { questionNumber: '2', chID: '111', cValue: 'red' },
  { questionNumber: '2', chID: '112', cValue: 'green' },
  { questionNumber: '2', chID: '113', cValue: 'blue' },
  { questionNumber: '3', chID: '114', cValue: 'yellow' },
  { questionNumber: '4', chID: '115', cValue: 'red' }
];

今回必要なのは、これら2つの配列のデータを一意のユーザーごとにグループ化する関数を書くことです。つまり、各ユーザーについて「質問とその選択肢」を1つのオブジェクトとして表現します。

期待される出力

最終的な出力は次のようなイメージになります。questionNumber が一致する選択肢データが、choice プロパティとして該当ユーザーのオブジェクトに紐づけられます。

const output = [
  {
    PersonalID: '11',
    questionNumber: '1',
    value: 'Something'
  },
  {
    PersonalID: '12',
    questionNumber: '2',
    value: 'whatever',
    choice: [
      { questionNumber: '2', chID: '111', cValue: 'red' },
      { questionNumber: '2', chID: '112', cValue: 'green' },
      { questionNumber: '2', chID: '113', cValue: 'blue' }
    ]
  },
  {
    PersonalID: '13',
    questionNumber: '3',
    value: 'anything',
    choice: [
      { questionNumber: '3', chID: '114', cValue: 'yellow' }
    ]
  },
  {
    PersonalID: '14',
    questionNumber: '4',
    value: 'null',
    choice: [
      { questionNumber: '4', chID: '115', cValue: 'red' }
    ]
  }
];

実装例:forEachで配列をマージする

基本となる実装は以下のとおりです。arr1 をコピーし、各要素に対して arr2 の中から questionNumber が一致する項目を集めて choice プロパティに格納します。

const mergeArray = (arr1 = [], arr2 = []) => {
  const copy = arr1.slice(); // 元の配列は変更しない

  copy.forEach(obj => {
    const helper = [];
    arr2.forEach(obj2 => {
      if (obj.questionNumber === obj2.questionNumber) {
        helper.push(obj2);
      }
    });
    if (helper.length !== 0) {
      obj.choice = helper;
    }
  });

  return copy;
};

console.log(JSON.stringify(mergeArray(arr1, arr2), undefined, 4));

コードのポイント

  • arr1.slice() でコピーを作成し、元の配列自体はそのまま保持します。
  • 内側のループで questionNumber が一致する要素だけを helper 配列に収集します。
  • helper が空でなければ、それを choice プロパティとして設定します。
  • なお slice() は浅いコピーなので、オブジェクトの中身まで独立させたい場合はスプレッド構文などを併用してください。

コンソールへの出力結果

上記のコードを実行すると、コンソールには次のように出力されます。

[
    {
        "PersonalID": "11",
        "questionNumber": "1",
        "value": "Something"
    },
    {
        "PersonalID": "12",
        "questionNumber": "2",
        "value": "whatever",
        "choice": [
            {
                "questionNumber": "2",
                "chID": "111",
                "cValue": "red"
            },
            {
                "questionNumber": "2",
                "chID": "112",
                "cValue": "green"
            },
            {
                "questionNumber": "2",
                "chID": "113",
                "cValue": "blue"
            }
        ]
    },
    {
        "PersonalID": "13",
        "questionNumber": "3",
        "value": "anything",
        "choice": [
            {
                "questionNumber": "3",
                "chID": "114",
                "cValue": "yellow"
            }
        ]
    },
    {
        "PersonalID": "14",
        "questionNumber": "4",
        "value": "null",
        "choice": [
            {
                "questionNumber": "4",
                "chID": "115",
                "cValue": "red"
            }
        ]
    }
]

より簡潔な書き方:map+filterを活用する

同じ処理は mapfilter を組み合わせると、より宣言的で読みやすいコードになります。

const mergeArray = (arr1 = [], arr2 = []) =>
  arr1.map(obj => {
    const choice = arr2.filter(
      item => item.questionNumber === obj.questionNumber
    );
    return choice.length > 0 ? { ...obj, choice } : { ...obj };
  });

{ ...obj } で毎回新しいオブジェクトを生成しているため、元の arr1 のオブジェクトを一切変更しない点もメリットです。

大量データ向けの最適化:Mapで事前グルーピング

上記の実装は二重ループとなり、計算量は O(n×m) です。データ件数が多い場合は、Map を使って arr2 をあらかじめキーごとにグルーピングしておくことで、O(n+m) まで効率化できます。

const mergeArray = (arr1 = [], arr2 = []) => {
  const grouped = new Map();
  for (const item of arr2) {
    if (!grouped.has(item.questionNumber)) {
      grouped.set(item.questionNumber, []);
    }
    grouped.get(item.questionNumber).push(item);
  }

  return arr1.map(obj => {
    const choice = grouped.get(obj.questionNumber);
    return choice && choice.length ? { ...obj, choice } : { ...obj };
  });
};

まとめ

  • 共通キー(questionNumber)を基準に、2つの配列をネスト構造のオブジェクトへマージできます。
  • 手軽さを重視するなら forEachmapfilter、大規模データなら Map による事前グルーピングが有効です。
  • 副作用を避けるためには、slice(){ ...obj } によるコピーを意識すると安全です。
  1. JavaScriptで2つのオブジェクトをマージする方法を解説

    JavaScriptでは、スプレッド構文(...)を使うことで、2つのオブジェクトを簡単に1つにマージ(結合)できます。各オブジェクトの前に「...」を付けて新しいオブジェクトリテラル内に展開するだけで、すべてのプロパティが統合されたオブジェクトが生成されます。以下に、2つのJavaScriptオブジェクトをマージするコードの例を示します。コード例<!DOCTYPE html> <html lang=ja> <head> <meta charset=UTF-8 /> <meta name=viewport content=width=dev

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

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