Object.keys() などに null や undefined を渡すと出ます。落ちるものと黙って通るものの違い、値がそこで null になった原因の追い方、落ちない受け取り方までを実行して確かめます。
Object.keys() などに null か undefined を渡すと出ます。
TypeError: Cannot convert undefined or null to object
example.js
for (const value of [null, undefined]) {
for (const [name, run] of [
['Object.keys', () => Object.keys(value)],
['Object.entries', () => Object.entries(value)],
['Object.assign', () => Object.assign({}, value)],
]) {
try {
run();
console.log(String(value), name, '-> 通る');
} catch (error) {
console.log(String(value), name, '->', error.name);
}
}
}
出力書き換えて実行できます
null Object.keys -> TypeError
null Object.entries -> TypeError
null Object.assign -> 通る
undefined Object.keys -> TypeError
undefined Object.entries -> TypeError
undefined Object.assign -> 通る
文言に undefined or null と両方書いてあります。 どちらなのかは分かりません。
まず渡している値そのものを出してください。
落ちるものと落ちないもの
ここが分かりにくいところです。 同じ Object の関数でも違います。
example.js
console.log(Object.assign({}, null, { a: 1 }));
console.log({ ...null, ...undefined, a: 1 });
出力書き換えて実行できます
{ a: 1 }
{ a: 1 }
example.js
console.log(Object.keys('ab'));
console.log(Object.keys(1));
console.log(Object.keys(true));
出力書き換えて実行できます
[ '0', '1' ]
[]
[]
Object.assign() は黙って無視します。
example.js
function summarize(source) {
console.log('受け取った値:', source);
return Object.keys(source ?? {}).length;
}
console.log(summarize({ a: 1, b: 2 }));
console.log(summarize(null));
出力書き換えて実行できます
受け取った値: { a: 1, b: 2 }
2
受け取った値: null
0
example.js
function keysOf(value) {
return Object.keys(value ?? {});
}
console.log(keysOf({ a: 1 }));
console.log(keysOf(null));
console.log(keysOf(undefined));
出力書き換えて実行できます
[ 'a' ]
[]
[]
スプレッド構文も落ちません。
だから「同じような書き方なのに、ここだけ落ちる」ことになります。
| 書き方 |
null / undefined を渡すと |
Object.keys() values() entries() |
例外 |
Object.assign() |
無視して通る |
{ ...value } |
無視して通る |
Object.freeze() |
そのまま返る |
文字列や数値なら落ちない
example.js
function keysOf(value) {
if (value == null) {
throw new TypeError('データが取得できていません');
}
return Object.keys(value);
}
console.log(keysOf({ a: 1 }));
try {
keysOf(null);
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
[ 'a' ]
TypeError: データが取得できていません
javascript
const rows = [{ a: 1 }, null, { b: 2 }];
rows.map((row) => Object.keys(row).length);
TypeError: Cannot convert undefined or null to object
落ちるのは null と undefined だけです。
ほかの値は、いったんオブジェクトとして扱われます。
文字列は添字が鍵になります。
意図せず文字列を渡していると、空でない配列が返って気づけません。
原因の追い方
「なぜそこが null なのか」が本体です。 落ちた行ではなく、値の出どころを見ます。
example.js
const rows = [{ a: 1 }, null, { b: 2 }];
console.log(rows.map((row) => Object.keys(row ?? {}).length));
出力書き換えて実行できます
[ 1, 0, 1 ]
example.js
console.log(Object.entries(null ?? {}));
console.log(Object.keys([]));
console.log(Object.keys([10, 20]));
console.log(Object.keys(() => {}));
出力書き換えて実行できます
[]
[]
[ '0', '1' ]
[]
よくある出どころは次のとおりです。
| 出どころ |
起きること |
| 通信の応答 |
本文が空、または null が返っている |
JSON.parse('null') |
例外にならず null が返る |
find() の結果 |
見つからないと undefined |
match() の結果 |
見つからないと null |
| 分割代入の取りこぼし |
鍵が無いと undefined |
JSON.parse('null') は特に気づきにくい形です。
→ SyntaxError: Unexpected end of JSON input
落ちない受け取り方
?? {} を挟むのがいちばん短いです。
example.js
try {
Object.keys(null);
} catch (error) {
console.log('渡した:', error.name);
}
try {
null.foo;
} catch (error) {
console.log('読んだ:', error.message);
}
出力書き換えて実行できます
渡した: TypeError
読んだ: Cannot read properties of null (reading 'foo')
|| ではなく ?? を使ってください。|| は 0 や空文字も置き換えます。
→ なぜ == ではなく === を使うのか
ただし、隠していいのかを先に考えてください。
「無くて当たり前」なら ?? {}、「無いのはおかしい」なら早く落とす。
?? {} を機械的に付けると、本当の原因が空の一覧として現れます。
一覧の途中に混ざっていると気づきにくい
3件のうち1件だけが null です。1件目は通っているので、
「動くときと動かないときがある」ように見えます。
守るなら、要素ごとに受けます。
件数が 0 で出てきたら、そこに null が混ざっていた合図です。
空を渡したときに何が返るか
配列を渡すと添字が鍵として返ります。関数は空です。
「空が返った」だけでは、元が何だったかは分かりません。
似ているエラーとの違い
| 文言 |
意味 |
Cannot convert undefined or null to object |
null / undefined をオブジェクトとして扱おうとした |
Cannot read properties of undefined |
undefined の中身を読もうとした |
x is not defined |
そもそも宣言が無い |
x is not a function |
関数でないものを呼んだ |
前の2つは近く見えますが、渡したのか読んだのかが違います。
このページの実行結果は Node 22.22.3 のものです。文言は処理系と版によって変わります。
探しかたの順序
- 渡している値をそのまま出す。
null か undefined かを確かめる
- その値の出どころまで遡る(通信・
find()・match()・分割代入)
- 無くて当たり前かを決める
- 当たり前 →
?? {} で受ける
- おかしい → その場で落として、原因が分かる文言を付ける
まとめ
- 落ちるのは
Object.keys() / values() / entries() など
Object.assign() とスプレッド構文は黙って無視する。 この差が切り分けを難しくする
- 文字列や数値では落ちない。文字列は添字が鍵になる
- 直すのは落ちた行ではなく、値の出どころ
?? {} で受けるか、早く落とすかを意図して選ぶ