JavaScript Object.keys() や Object.entries() でキーや値が取得できない

JavaScript の Object.defineProperty() で定義したプロパティで Object.keys()Object.entries() でキーや値が取得できない場合の対応方法です。

やりたいこと

以下のように Object.defineProperty() でプロパティと値が定義されたオブジェクトがあり、

const obj = {};
Object.defineProperty(obj, "key1", { value: "value1" });

以下のようにオブジェクトのプロパティ名の一覧を取得したかったのですが、空配列が返ってきてしまいました。

Object.keys(obj); // []

以下のように Object.entries() も試しましたが空配列でした。

Object.entries(obj); // []

Object.keys() は列挙可能なプロパティの配列を返す

MDN で Object.keys() の説明をちゃんと読んでみると以下の記載がありました。

Object.keys() は、object で直接発見された列挙可能なプロパティに対応する文字列を要素とする配列を返します。 - Object.keys() - JavaScript | MDN

列挙可能?

なお、 Object.entries() にも同様の記載があります。

Object.entries() は、object に直接存在する文字列をキーとした列挙可能プロパティの組 [key, value] が配列要素に対応した配列を返します。 - Object.entries() - JavaScript | MDN

列挙可能とは

さて、先ほどのオブジェクトの定義を以下のように変更してみます。

const obj = {};
obj.key1 = "value1";

普通はこのように定義することが多いと思いますが、この場合は Object.keys()Object.entries() は期待通りの挙動をします。

Object.keys(obj)  // ["key1"]
Object.entries(obj) // [["key1", "value1"]]

ということは Object.defineProperty() に問題がありそうです。

改めて MDN で Object.defineProperty() を確認してみます。

Object.defineProperty(obj, prop, descriptor);
obj
プロパティを定義するオブジェクトです。
prop
定義または変更するプロパティの名前または Symbol です。
descriptor
定義または変更するプロパティの記述子です。 - Object.defineProperty() - JavaScript | MDN

プロパティの記述子とは?と思い読み進めてみると…。ありました!

enumerable
true である場合のみ、このプロパティは対応するオブジェクトでのプロパティ列挙に現れます。 既定値は false です。 - Object.defineProperty() - JavaScript | MDN

enumerable をプロパティの記述子内で指定していなかったので、デフォルトの false になり、列挙可能ではないプロパティになり、Object.keys() や Object.entries() の結果に現れなかったようです。

Object.defineProperty() で列挙可能なプロパティを定義する

ということで、プロパティの記述子の enumerabletrue にしてプロパティを定義したところ、Object.keys()Object.entries() でキーや値が取得できるようになりました。

const obj = {};
Object.defineProperty(obj, "key1", { value: "value1", enumerable: true });
Object.keys(obj)  // ["key1"]
Object.entries(obj) // [["key1", "value1"]]

なお、Object.getOwnPropertyNames() を使用すれば、プロパティの記述子の enumerable によらず、全プロパティー名を配列で取得することが可能です。

const obj = {};
Object.defineProperty(obj, "key1", { value: "value1" });
Object.getOwnPropertyNames(obj) // ["key1"]

Object.keys() は列挙可能なプロパティに限定できるので、厳密なオブジェクトを定義したければ、Object.defineProperty() のプロパティの記述子でコントロールし、Object.keys() で列挙するほうが小回りは効きそうです。

ちなみに JavaScript のビルトインオブジェクトはプロパティの記述子で enumerablefalse になっており、列挙可能ではないプロパティがほとんどですが、そのプロパティの記述子で configurabletrue であれば、Object.defineProperty() で列挙可能に変更することができます。

const err = new Error("Invalid.");
Object.keys(err); // []

// プロパティの記述子を確認
Object.getOwnPropertyDescriptor(err, "message");
/**
 * {
 *   value: "Invalid.",
 *   writable: true,
 *   enumerable: false,
 *   configurable: true
 * }
 */

// enumerable を true に変更
Object.defineProperty(err, "message", { enumerable: true });
Object.keys(err); // ["message"]

あとがき

わざわざ Object.defineProperty() でプロパティ設定とかあまり需要なさそうですが、列挙可能とか知らなかったのでメモとして残してみました。