「オブジェクトのキーの順序を保証したい❗」
そんな人向けの記事。
「そもそもオブジェクトのキーって順序は保証されるんじゃないの❓」
されない。
↓のようにキーに数値(の文字列)を指定すると、定義したときの順序とは異なる。
Object.keys()
でキーを取得するときも↑のような順序で取得されることになる。
まぁ、キーが数値になるなんてレアケースだと思うんだけど、
レアケースだからこそ、この仕様を忘れてハマることになりそう。
怖い❗
解決策1: Map()
を使う
Map()
は↑のような 糞仕様 へんてこ仕様がないので、
安全に使える。
ちなみに、パフォーマンスもいいらしい。
MDN によると↓。
Performs better in scenarios involving frequent additions and removals of key-value pairs.
ただ、オブジェクトではなくなるので、
既にオブジェクトで実装しちゃったコードを
Map()
で置き換える場合は多少の手間がかかる。
解決策2: TypeScriptで配列とオブジェクトに分ける
キーだけは配列で定義しておいて、
オブジェクトのキーは配列で定義したものを使うようにする。
const order = [`a`, `1`] as const;
type MyObject = { readonly [K in (typeof order)[number]]: any };
const object: MyObject = {
a: 2,
1: { unchi: `de soul` },
};
順序が必要なときは配列のほうを参照すれば良い。
これ、単純に考えると二重管理になっちゃって良くないと思われるかもしれない。
しかし、TypeScriptなら問題ない。
オブジェクトのキーを型として強制できるので、
片方の定義をミスっても必ずエラーを吐いてくれる。
例えば、↓はオブジェクト側で 1
のキーを指定し忘れた場合。
VSCodeならリアルタイムで赤線が引かれる。
(確認してないけど) tsc
とかでトランスパイルしたときもエラーが出ると思う。
解決策3: 割り切る
あんまりな解決策だけど、結構重要かもしれない。
数値(の文字列)をキーにするなんてレアケースだし、
設計によっては「絶対ない❗」って言い切れることのほうが多いと思う。
TSじゃなくてJSで書いてて、かつ Map()
も使えないって場合も割り切ったほうが早そう。
余談
自分は解決策2を採用した。
Stable Diffusion用にキャラ用のプロンプトをボタンぽちぽちで構築するアプリ を作ったときの話。
プロンプトはカテゴリごとにまとめてWeb画面上で上から順に表示したいってのが要求。
カテゴリの順序が勝手に変わるのは困るなと思ったわけ。
当初は↓みたいにただのオブジェクトとして定義してた。
const defines = {
"カテゴリ1" : [
{prompt: `ナイスなプロンプト`}
{prompt: `えちちなプロンプト`}
],
"カテゴリ2" : [
{prompt: `えぐい性癖のプロンプト`}
]
...
} as const;
が、順序が保証されない仕様に気づいて↓のように改善した。
export const allCategories = [
`カテゴリ1`,
`カテゴリ2`,
...
] as const;
export defines: { [K in Category]: {prompt: string} } = {
"カテゴリ1" : [
{prompt: `ナイスなプロンプト`}
{prompt: `えちちなプロンプト`}
],
"カテゴリ2" : [
{prompt: `えぐい性癖のプロンプト`}
]
...
}
詳細は コード みて。
勘のいい人はもう気づいてると思うけど、これは解決策3で全く問題なかったケースだね❗
一端 Map()
で書き直してまた戻したりしたので、タイパが悪かったね〜。