# Setでオブジェクトの重複が消えない理由｜比較するキーと残す値を決める

同じidのオブジェクトをSetに入れても重複が残るのはなぜ？参照の同一性、Mapで使う複合キー、先と後の入力のどちらを残すかを、単独実行できる例で確認します。

正規URL: https://3mikan.com/archives/6090

著者: みかん
公開: 2026-09-08T15:06:00.000Z
更新: 2026-09-08T15:06:00.000Z

一覧の重複を取り除こうと`new Set(records)`を使ったのに、同じIDの行が残っている。これは`Set`が壊れているのではなく、**オブジェクトの中身を比較しているわけではない**ためです。

IDを基準にまとめたいなら、そのIDを比較用のキーにします。ただし、そこで終わりではありません。IDがどの範囲で一意なのか、同じキーの行が来たらどちらを残すのかも決める必要があります。

## まずは手元で再現する

[実行用サンプルを開く・保存する](/examples/object-deduplication.mjs)から、`object-deduplication.mjs`という名前で保存して実行してください。

```sh
node object-deduplication.mjs
```

確認環境はNode.js v24.14.0です。追加パッケージ、アカウント、ネットワーク通信、ファイルの書き込みは不要です。最後に「すべての実行例が期待値と一致しました。」と出れば、重複判定と残す値、入力の不備まで照合できています。以下のJavaScriptの枠も、それぞれ別の`.mjs`ファイルで実行できます。

## 同じ内容の別オブジェクトは、Setでは別の値

```js
const same = { id: 'row-1' };
console.log(new Set([same, same]).size); // 1
console.log(new Set([{ id: 'row-1' }, { id: 'row-1' }]).size); // 2
console.log(new Set([1, 1, '1']).size); // 2
console.log(new Set([NaN, NaN]).size); // 1
```

最初の例は、同じオブジェクトを指す参照を2回入れています。次の例は、同じ内容のオブジェクトを二つ作っています。この違いが、1件になるか2件残るかを分けます。

`Set`はオブジェクトを参照の同一性で比較します。各プロパティをたどって中身が同じかを調べる処理ではありません。また、数値の`1`と文字列の`"1"`は別の値で、`NaN`同士は同じ値として扱われます。[MDNのSetの同一性規則](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#value_equality)

## idだけでよいか、一意になる範囲を確認する

今回の実行用サンプルでは、データの所属先を`network`、その中でのIDを`id`とし、次の3行を扱います。名前は説明用の架空の値です。

| network | id | value |
| --- | --- | --- |
| alpha | row-1 | 10 |
| beta | row-1 | 20 |
| alpha | row-1 | 30 |

ここでは`alpha`と`beta`で同じIDが使われても、別の記録として残したいとします。`id`だけをキーにすると、3行とも同じキーへまとめられ、必要な`beta`の行まで失います。

採用するルールは「空でない文字列の`network`と`id`の組が同じなら重複」です。これはこのデータのために決めたルールで、あらゆるAPIのIDがこの組で一意になるという意味ではありません。

## Mapに入れる配列も、参照で比較される

二つの値を組にしたくても、新しく作った配列をそのまま`Map`のキーへ使うと、再び参照の比較になります。

```js
const byPair = new Map();
byPair.set(['alpha', 'row-1'], 10);
byPair.set(['alpha', 'row-1'], 30);
console.log(byPair.size); // 2
console.log(byPair.get(['alpha', 'row-1'])); // undefined
```

これらは中身が同じでも別の配列です。`Map`のオブジェクトキーも、内容ではなく参照で比較されます。[Map.setのキーの説明](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set)

実行用サンプルでは、型を確認した二つの文字列を`JSON.stringify([row.network, row.id])`で一つの文字列にしています。単純に`network + '|' + id`とつなぐ方法と違い、値に区切り文字が含まれても、どこで分かれるかを表現できます。

実際に`["a|b", "c"]`と`["a", "b|c"]`を別のキーとして残すことを確認しています。文字列の組という条件で使う方法であり、任意のオブジェクト全体をJSON化すれば正しく重複比較できる、という勧めではありません。

## 先の入力を残すか、後の入力へ更新するか

キーが決まったら、同じキーが来たときの扱いを決めます。次の例は実行用サンプルの処理と同じ考え方で、型のそろった小さな入力に対して二つの方針を比べます。

```js
const records = [
  { network: 'alpha', id: 'row-1', value: 10 },
  { network: 'beta', id: 'row-1', value: 20 },
  { network: 'alpha', id: 'row-1', value: 30 },
];
const first = new Map();
const last = new Map();

for (const row of records) {
  const key = JSON.stringify([row.network, row.id]);
  if (!first.has(key)) first.set(key, row);
  last.set(key, row);
}

console.log([...first.values()].map((row) => row.value).join(',')); // 10,20
console.log([...last.values()].map((row) => row.value).join(','));   // 30,20
console.log(first.values().next().value === records[0]); // true
```

先の入力を残す場合は、まだそのキーがないときだけ追加します。後の入力を残す場合は、同じキーの値を更新します。どちらが正しいかは、データを使う処理の目的で決まります。

`Map`で既存キーの値を更新しても、そのキーが最初に入った位置は変わりません。そのため後の入力を残した結果は`30,20`で、`20,30`にはなりません。値を選ぶルールと表示順を並べ替えるルールは別です。[Mapの挿入順](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#description)

また、ここでいう「後」は入力配列で後ろにあるという意味です。観測時刻や更新時刻が新しいとは限りません。本当に新しい記録を残したいなら、時刻や版番号をどう比較するかを別に定めます。

## 重複を消す前に、残すための条件を決める

実行用サンプルの`deduplicate()`は、`network`か`id`が空文字列・欠落・文字列以外ならエラーにします。入力不備のある行を、まとめて同じキーにして黙って落とさないためです。`first`と`last`以外の方針名も受け付けません。

結果を新しい配列にしても、残した各行は元のオブジェクトへの参照です。重複除去はデータ全体のコピーではないため、後で行を書き換える処理があるなら、その扱いも分けて考えます。

比較する範囲、同じキーで残す値、出力順。この三つを決めると、「Setに入れたのに減らない」だけでなく、「必要な別の行まで消えた」という問題も防ぎやすくなります。

## 確認した一次情報

- [MDN: Setの値の同一性](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set>): 確認日 2026-09-08
- [MDN: Mapのキーと挿入順](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map>): 確認日 2026-09-08
- [MDN: Map.setによる追加と更新](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set>): 確認日 2026-09-08
