【JavaScript】dictionaryとは?オブジェクトやMapで実現する方法を初心者向けにわかりやすく解説

はじめに

皆さんはJavaScriptでデータを管理するとき、連想配列のようにキーと値を対応させて扱いたいと感じる場面はないでしょうか。
例えばユーザー情報や設定値をまとめて保管するときに「dictionary(辞書型)」があれば便利だと考えるかもしれません。
実はJavaScriptには、Pythonのような“dictionary”という名称の型は存在しませんが、似たような働きをする構造としてオブジェクトMapがあります。

JavaScriptのオブジェクトは多くの方がご存じかもしれませんが、実はオブジェクトとMapには微妙な使い分けの違いがあります。
また、オブジェクトを使った情報のまとめ方にはちょっとした落とし穴があり、初心者の方は戸惑いやすい箇所もあるかもしれません。

この記事では、JavaScriptにおけるdictionary的なデータ構造として、オブジェクトやMapを使うメリットや実装方法を初心者向けに丁寧に解説します。
実際の業務で使う際の注意点なども交えつつ、基本から順番に見ていきましょう。

この記事を読むとわかること

  • JavaScriptにおけるdictionary(辞書型)とは何か
  • オブジェクトとMapでキーと値をどのように保管・取得するのか
  • dictionary的データ構造を実務で使うときの注意点や活用シーン
  • オブジェクトとMapの使い分けポイント

JavaScriptのdictionaryとは

JavaScriptを学び始めた方からすると、「Pythonにあるようなdictionaryが使いたい」と思うかもしれません。
ただし先ほど触れた通り、JavaScriptには「dictionary」という名称のデータ型はありません。

しかし、オブジェクトやMapを使うことで、辞書型と同じようにキーと値のペアを管理できます。
ここでは「dictionary」という概念をJavaScriptでどう表現できるのか、具体的に見ていきます。

JavaScriptにおけるdictionaryの一般的なイメージ

JavaScriptでdictionaryを実現するとき、もっとも基本的なのがオブジェクトです。
例えば次のようなイメージを思い浮かべてみてください。

const user = {
  name: "Taro",
  age: 25,
  country: "Japan"
};

ここでuserというオブジェクトは、nameagecountryといったキー(プロパティ)と、それぞれの値を紐づけています。
つまり「キーと値をセットで管理する」という点においては、dictionary的な役割を果たすわけです。

ただし、JavaScriptのオブジェクトはクラス的な概念を内包しており、ただの連想配列として使うことには少し注意が必要です。
その一方で、ES6(ECMAScript 2015)で導入されたMapを使うと、連想配列的な操作がよりシンプルになります。
たとえばMapはキーに何でも使えますし、要素数を確認するときもmap.sizeのようにプロパティで簡単に取得できます。

dictionary的な構造を使うメリット

dictionary、すなわちキーと値を対応づけるデータ構造を使うメリットとしては、以下のような点が挙げられます。

  • 値を素早く取得しやすい
  • データ同士の関連づけがシンプル
  • 追加や更新が直感的に行える

実務では、APIから返ってくるデータを一括で管理したり、ユーザーの入力データをキーごとにまとめたりするような場面で役立ちます。
扱うデータ量が増えたとしても、キーを経由して目的の情報を探す形になるため、同じ構造を繰り返し利用しやすいのが便利なポイントでしょう。

dictionaryをオブジェクトで実現する

JavaScriptを学び始めたばかりの方が最初に思いつく方法は、オブジェクトを使うことではないでしょうか。
ここでは「オブジェクトの基本構文」から「参照と更新の仕方」、そして「オブジェクトをdictionary的に使うときの注意点」などを見ていきます。

オブジェクトの基本構文

オブジェクトは中括弧{}を使って宣言し、その中にキーと値をコロン:で対応づけて書きます。
さらにキーと値のペアをカンマ,で区切ることで複数のプロパティを定義できます。

const person = {
  firstName: "Hanako",
  lastName: "Sato",
  age: 30
};

この例であれば、personというオブジェクトが「firstNameHanako」、「lastNameSato」、「age30」というペアをもっています。
また、オブジェクトリテラル内でキーを文字列として明示してもOKですが、JavaScriptでは上記のように書くことが多いです。

オブジェクトの参照と更新

オブジェクトのプロパティは次の2通りの方法で参照できます。

  1. ドット記法: obj.key
  2. ブラケット記法: obj["key"]
console.log(person.firstName);      // "Hanako"
console.log(person["lastName"]);    // "Sato"

変更するときも同様で、単に値を代入し直すだけでOKです。

person.age = 31;                // ドット記法
person["lastName"] = "Tanaka";  // ブラケット記法

ドット記法はコードがスッキリしやすく、キーが文字列リテラルそのものであればよいのですが、変数をキーとして利用したい場面ではブラケット記法を使う必要があります。
また、キーとして使いたい文字列にハイフンなどの特殊文字が含まれる場合も、ブラケット記法でしか扱えません。

dictionary的に使うときの注意点

オブジェクトは非常に便利ですが、dictionary代わりに利用するときには以下のような注意点があります。

  • プロトタイプチェーンによる意図しないプロパティの存在
  • キーには主に文字列しか使えない
  • キーの列挙順に保証がない場合もある
  • 内部的にはオブジェクトが持つメソッドなどと混在する可能性

このあたりの仕組みを知らないと、思わぬキーが混ざるケースがあります。
また、確実に純粋な「キーと値だけ」のデータを扱いたいなら、後述するMapを検討するのがよいでしょう。

dictionaryをMapで実現する

続いて紹介するのが、ES6で導入されたMapです。
Mapは「キーと値の関連付け」をより直接的に表現できる構造であり、実質的には連想配列のような使い方が可能です。
オブジェクトとの違いを意識しつつ、Mapの特徴を確認してみましょう。

Mapの基本構文

Mapはコンストラクタで生成します。
例えば空のMapを作る場合は次のようになります。

const myMap = new Map();

また、生成時にキーと値の初期セットを渡すこともできます。
以下のように二次元配列の形で[key, value]を並べればOKです。

const myMap2 = new Map([
  ["apple", "りんご"],
  ["banana", "バナナ"]
]);

これは、キーが"apple"、値が"りんご"というペアと、キーが"banana"、値が"バナナ"というペアを持ったMapです。

Mapの参照と更新

Mapに値を追加したいときはset()、値を取得したいときはget()を使います。
オブジェクトのときと違い、ドット記法やブラケット記法は使いません。

myMap.set("orange", "オレンジ");
const result = myMap.get("orange"); 
console.log(result); // "オレンジ"

キーが存在するかどうか調べるにはhas()を使います。
キーと値を削除したい場合はdelete()、すべて削除するならclear()です。
さらに現在格納されているペア数を調べるにはsizeプロパティを参照します。

console.log(myMap.has("orange")); // true
myMap.delete("orange");           // キー"orange"のペアを削除
myMap.clear();                     // すべて削除

Mapをdictionary的に使うメリット

Mapがdictionary的に便利な理由は次のとおりです。

  • キーにあらゆる型を指定できる(文字列以外も可)
  • 連想配列としての操作メソッド(set(), get(), has()など)が充実
  • サイズをプロパティ(size)で簡単に把握可能
  • プロトタイプチェーンの影響を受けにくい

オブジェクトの場合はキーは文字列(もしくはシンボル)に限られますが、Mapなら数値やオブジェクト自体をキーにすることさえ可能です。
これは、たとえばDOM要素をキーにして、それに紐づく情報をまとめて保存するといった場面で有用です。

実務での活用シーン

dictionary的なデータ構造が活躍する場面として、具体的にどのようなケースがあるのでしょうか。
以下では実務における代表的な活用例を見ていきます。

フォーム入力値をまとめて管理する

Webアプリでは、ユーザーが入力したデータをまとめて扱う必要があります。
たとえば「ユーザーの名前」「メールアドレス」「パスワード」など、フォームの各入力要素から取得した値を一旦dictionary形式で保持すると管理しやすいです。

const formData = {
  userName: "佐藤太郎",
  email: "taro@example.com",
  password: "abcdefg"
};

まとめて扱うことで、データチェックやAPIへ送信する際などに取り回しが良くなります。
もしキーに変数を使いたい場面が多いなら、Mapを使う方が柔軟かもしれません。

APIレスポンスを整理して扱う

APIレスポンスによっては、複雑なJSONデータが返ってくる場合があります。
そのデータをキーごとに整形し直して、自分のアプリで使いやすい形にまとめておきたいことがあります。

ここでdictionary的なデータ構造を用いると、取得したデータを再構成しやすくなるわけです。
例えばユーザーリストが配列で帰ってきたら、ユーザーIDをキーにしてMapに詰めることで、高速に検索できるようにすることも可能です。

const usersArray = [
  { id: 1, name: "Taro", active: true },
  { id: 2, name: "Hanako", active: false }
];

const usersMap = new Map();
usersArray.forEach(user => {
  usersMap.set(user.id, user);
});

こうするとusersMap.get(1)のようにして、IDが1のユーザー情報をすぐに取得できます。
IDの重複がなければ、連想配列構造が非常に相性いいですよね。

エラーコードやメッセージの管理

実務でAPI開発を行う場合、特定のエラーコードに対応するメッセージを一括で管理したい場面があります。
例えば以下のように、エラーコードをキーとして、ユーザーに提示すべきメッセージを値として格納しておく方法です。

const errorMessages = new Map([
  [400, "リクエストが不正です"],
  [401, "認証が必要です"],
  [404, "ページが見つかりません"],
  [500, "サーバー内部エラーが発生しました"]
]);

function showError(code) {
  const message = errorMessages.get(code) || "原因不明のエラーが発生しました";
  console.log(message);
}

このようにしておけば、コードをキーに取り回すだけで適切なメッセージを表示できます。
オブジェクトでも同じことはできますが、Mapを使うとキーが数値でも扱いやすい点が魅力です。

オブジェクトとMapの使い分け

オブジェクトかMapか。
dictionary的なデータ構造を扱う上で、この選択に迷う方は少なくありません。
ここでは、利用シーンに合わせた使い分けの基準を示します。

もしキーが文字列(またはシンボル)限定でOKなら

キーが文字列のみで問題ないなら、オブジェクトで十分事足りるケースも多いです。
例えば一般的な設定値やユーザー情報をまとめるだけなら、以下のような単純なオブジェクトで問題ないでしょう。

const config = {
  environment: "production",
  version: "1.0.0"
};

オブジェクトの方が既存のライブラリとの親和性が高いこともありますし、短い記法で書けるというメリットもあります。
プロパティの補完機能など、エディタによってはコード補完が効きやすい点も魅力です。

もしキーの型を自由に扱いたいなら

一方で、キーに数値やオブジェクト自体を使いたい場面があるなら、Mapを選ぶのがおすすめです。
また、データの削除・検証が頻繁に行われる場合、has()delete()といった専用メソッドが用意されているMapの方が扱いやすいでしょう。

さらにMapの場合はプロトタイプチェーンの影響を受けにくく、キーの列挙順もオブジェクトより安定しています。
大規模なアプリで、連想配列的な使い方をメインとするならMapを採用すると無難です。

dictionary操作時のよくある疑問や注意点

実際にdictionary的なデータ構造を扱うとき、初心者の方はどんな疑問を持つことがあるでしょうか。
ここでは代表的なものをいくつか取り上げます。

キーの重複と型の違い

オブジェクトの場合、同じキーを2回定義すると、後から定義したものが有効となり、前の値は上書きされます。
Mapも同様に後からセットすれば上書きとなります。
もしキーがユニークになる保証がない場合、格納前にhas()やオブジェクトならif文で確認するなどの対策を取る必要があります。

また、オブジェクトではキーは暗黙的に文字列化されるため、数値キーを使ったつもりでも実際には文字列になっています。
Mapなら文字列化されずにそのままの型で保持される点を意識しましょう。

パフォーマンス面での考慮

オブジェクトもMapも、大抵のケースではキーと値の取得・更新が高速に行えます。
ただし、キーの数が非常に多い、頻繁に追加や削除を繰り返す、といった状況では、Mapの方が安定したパフォーマンスを期待できる場合があります。

一方で、単に設定値を数個まとめる程度の使い方なら、オブジェクトの方がシンプルで可読性も高いでしょう。
要するに、求める柔軟性とデータ量に応じて最適な構造を選ぶことが重要です。

dictionaryを活用する際のエラー例

初心者の方がdictionaryを使うとき、予期せぬエラーに直面することがあります。
ここではよくある例を挙げます。

想定外のキーが存在してしまう

オブジェクトに対してfor...inループを回したとき、プロトタイプチェーン上のプロパティまで列挙されることがあります。
これにより、意図しないキーが混ざってしまうことがあるので注意が必要です。

もしオブジェクトで純粋なキーだけを取り出したいなら、Object.keys()Object.entries()を使う方が安全です。
また、プロトタイプをまったく持たない“純粋なディクショナリ”が必要な場合は、Object.create(null)で生成する方法もあります。

const pureObj = Object.create(null);
pureObj["key"] = "value";
console.log(Object.keys(pureObj)); // ["key"]

ここではprototypeが継承されないため、不要なプロパティが混ざる心配はありません。

undefinedの扱いで混乱する

Mapやオブジェクトでキーを検索したときに、キーが存在しないとundefinedが返ってきます。
例えば次のようなケースがありがちです。

const conf = { mode: "dark" };
if (conf.foo === undefined) {
  console.log("このキーはありません");
}

単にキーが存在しないだけなのか、キーは存在するけれど値がundefinedなのかが区別つかない場面では混乱の元になります。
アプリによってはhas()を使うか、もしくはin演算子などで「プロパティが実際に存在するのか」をチェックすると明確に判別できます。

console.log("mode" in conf);  // true
console.log("foo" in conf);   // false

Mapの場合はmyMap.has("foo")のようにメソッドで直接チェックすれば済むため、こちらの方が分かりやすいかもしれません。

オブジェクトをdictionary代わりに使う場合、プロパティの存在チェックには「in」演算子や「Object.hasOwn()」を活用するとスッキリしたコードになります。

実務レベルでのデータ構造の選択ポイント

実務レベルでは、単にdictionaryを使うだけでなく、どのようなデータ構造が全体設計と噛み合うかを考える必要があります。
例えば「最終的にサーバーへ送信するときはJSON化する」「関数に渡すデータは型がきっちりしていた方が良い」といった要件によって、オブジェクトの方が扱いやすい場合があります。

逆に、ユーザー操作によって動的に要素を増減する機能が多かったり、キーが文字列以外にも広がる見込みがあったりするなら、Mapを使った方がミスが少ないです。
またフレームワークによっては、オブジェクトでのデータ管理を前提として作られている場合もあるため、その辺りも合わせて検討しましょう。

ただデータを入れるだけなら動いてしまう場合も多いので、一見不具合に気づきにくいです。
しかし後から拡張するときに「オブジェクトで制限がかかってしまった」「Mapでないと困った」などの課題が生じる可能性があります。
最初に用途をしっかり見極めることが大切です。

まとめ

ここまで、JavaScriptでdictionary的なデータ構造を実現する方法について、オブジェクトとMapの両面から解説してきました。
初心者の方はどうしても「オブジェクト=連想配列」と認識しがちですが、実際にはオブジェクトはプロトタイプチェーンなどクラス的な概念を持つため、単純にキーと値を管理する用途には注意が必要な場面もあります。

一方でMapなら、キーに文字列以外も使えたり、サイズ取得や削除などの操作がしやすかったりといった利点があります。
ただし、シンプルな設定値を管理するだけならオブジェクトで十分なことも多く、あまり規模の大きくないプログラムではオブジェクトの方が扱いやすい場合もあるでしょう。

dictionary的なデータ構造は、実務において入力値の管理やAPIレスポンスの処理など、さまざまな場面で活用されます。
どちらが優れているかというよりは、用途やデータ量、キーの多様性に応じてオブジェクトとMapを使い分けることが大事です。

皆さんの開発環境や要件に合わせて、最適な方法でdictionaryを実現し、データを整理して活用してみてください。

JavaScriptをマスターしよう

この記事で学んだJavaScriptの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。