JavaScriptで、キーと値の対応を取るような構造を考えていたときに、少し悩むことがありました。
他言語と比較して
RubyやJavaでは、Hash
のキーには文字列や数値だけでなく、任意のオブジェクトを入れることができます。そして、「オブジェクトとしての同一性」以外に「内容の同一性」を考える、別次元での等値判定が用意してあります。
一方で、JavaScriptには連想配列を実現する手段として、「Object
」と「Map
」があります。詳しい記事があるので中身は省略すると、
-
Object
…キーは文字列しか使えない、toString
や__proto__
のように特殊な動作をするキーもある -
Map
…オブジェクト型についてはオブジェクトの同一性判定しかない
という状況なので、(JavaScriptにそもそもオブジェクトが「値として同一」だということを判定する仕組み自体ないですが)「値として同一」なオブジェクトをキーに処理を行いたい、となると、なかなかうまく行きません。
JSON化の罠
幸い、今回キーにしたいオブジェクトは、「Object
」「文字列」「配列」「数値」「ブール値」「null
」を組み合わせた、JSONにできるオブジェクトなので、「JSONをキーにすれば適当な方法で管理できるんじゃないか」とも、いったんは思いました。
ただ、RubyやPHPでは連想配列の順序が保証されるのに対して、JavaScriptもJSONも、キーの順序は不定となっています。つまり、「キーも値もすべて同じ」オブジェクトがあったとして、それをJSON化しても、JSONがどんな順序になるのか制御する方法もないわけで、単純な手法では同じJSONを生成することができません。
// falseとなる可能性が高いけど、それも保証されない
console.log(JSON.stringify({foo: '1', bar: 2}) === JSON.stringify({bar: 2, foo: '1'}));
探せばあった
そこで行き詰まっていたところ、npmにjson.sortifyというものがありました。名前のとおり、キーをソートしてJSONを生成するルーチンで、「A deterministic version of JSON.stringify
」、つまり同じキーと値の組み合わせなら必ず同じJSON文字列を生成する、とありました。今回やりたいことにぴったりです。
…とおもったら、JavaScriptとしての規定がないからわざわざ入れようというはずなのに、「先に入れたキーが先に出力される」というJavaScriptエンジンの挙動に依存していて、「これはどうなんだ」という気分になってしまいました。