JS: オブジェクトのキーの順番は「整数っぽいキーが先・昇順」
Object.keys・for...in・JSON.stringify などが返すキーの順番は、ただの追加順ではない。配列の添字として有効な整数のキー(0〜4294967294)が先に数値の昇順で並び、そのあとに残りの文字列キーが追加順で並ぶ。
困る場面
ID をキーにしたオブジェクトで「サーバーから返ってきた順」や「追加した順」を保ちたいときに、勝手に並べ替わる。
js
const byId = {};
byId["30"] = "c";
byId["b"] = "x";
byId["10"] = "a";
byId["2"] = "b";
Object.keys(byId); // ["2", "10", "30", "b"]ルール(仕様の OrdinaryOwnPropertyKeys)
- 配列の添字になれるキー(
"0"〜"4294967294"、つまり 2^32 − 2 まで)を数値の昇順 - それ以外の文字列キーを追加した順
- Symbol キーを追加した順(
Object.keysやfor...inには出ず、Reflect.ownKeysなどで見える)
"4294967295"(2^32 − 1)以上、"-1"、"01"、"1.5" は「整数っぽく」見えても文字列キー扱いで、追加順に並ぶ。
js
Object.keys({ b: 1, "4294967295": 2, "4294967294": 3, a: 4, "01": 5 });
// ["4294967294", "b", "4294967295", "a", "01"]対策
- 順番に意味があるなら
Mapを使う。Mapはキーの型に関係なく追加順を保つ。 - オブジェクトのままにするなら、順番は別に配列(
ids: string[])で持つ。 - どうしてもキーで済ませたいなら
"id_30"のように接頭辞を付けて整数扱いを避ける。
注意点
JSON.parseの結果も同じルールで並ぶので、API が返した JSON オブジェクトのキー順はそのまま信用しない。- この順番は ES2015 以降の仕様で決まっていて、
for...inとObject.keysの順も ES2020 で揃えられた。
出典
- @xah_lee(js tip. order of properties, with a magic number 4294967294)