- TypeScript
TS7053 オブジェクトに文字列キーでアクセスすると出る型エラーの直し方
TS7053 とは、obj[key] の key の型(多くは string)が広すぎて、「そのキーが obj にあるとは言い切れない」ときに出るエラーです。 直し方は大きく 2 方向あります。キーが決まっているオブジェクトならキーの型を狭める(keyof typeof)、キーが実行時に増えるならオブジェクトの型を広げる(Record や Map)です。
この記事では 8 通りの直し方を、「キーが固定か可変か」「存在しないキーを渡したときにどうなるか」で比べます。すべて tsc の出力全文と node の実行結果を載せているので、自分のコードに近いものをそのまま使えます。
確認環境:Ubuntu 24.04、Node.js 22.22.2、TypeScript 7.0.2(6.0.3 と 5.9.3 でも同じエラーになることを確認)。
TS7053 の直し方早見表(8 通りの比較)
| # | 直し方 | キー | 存在しないキーを渡すと | 向いている場面 |
|---|---|---|---|---|
| 1 | keyof typeof obj を引数の型にする |
固定 | コンパイルエラー(TS2345)で止まる | 定数の表から値を引く |
| 2 | K extends keyof T のジェネリクス関数 |
固定 | コンパイルエラー(TS2345)で止まる | どのオブジェクトにも使う汎用の取得関数 |
| 3 | Record<string, V> で注釈する |
可変 | エラーなし。実行時は undefined(型は V) |
後からキーが増える辞書 |
| 4 | インデックスシグネチャ [key: string]: V |
可変 | エラーなし。実行時は undefined(型は V) |
固定のプロパティと可変のキーを同居させる |
| 5 | Object.hasOwn を使った型述語で絞る |
固定 | else 側に進む(実行時に判定) |
入力値・URL パラメータなど外から来る文字列 |
| 6 | as const + satisfies |
固定 | コンパイルエラーで止まる | ラベルや設定の定数表。値の型も固定したい |
| 7 | Map<string, V> に置き換える |
可変 | get の戻り値が V | undefined になり、確認を促される |
実行時に追加・削除する表 |
| 8 | key as keyof typeof obj で断定する |
固定 | エラーなし。実行時は undefined(型は V) |
キーの正しさを別の場所で確かめ済みのとき |
迷ったら「外から来る文字列は 5、コードに直接書くキーは 1、中身が増える表は 7」と覚えておくと選びやすくなります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TS7053 オブジェクトに文字列キーでアクセスすると出る型エラーの直し方」
TS7053 を再現する(エラー全文)
tsconfig.json は次の最小構成です。
{
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"types": []
},
"include": ["*.ts"]
}
// e01.ts
const prices = { apple: 120, banana: 80, orange: 100 };
function getPrice(name: string) {
return prices[name];
}
const key: string = "apple";
console.log(prices[key]);
for (const k in prices) {
console.log(k, prices[k]);
}
Object.keys(prices).forEach((k) => {
console.log(k, prices[k]);
});
console.log(getPrice("apple"));
console.log(prices["grape"]);
export {};
npx tsc --noEmit の出力です。
e01.ts(4,10): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
e01.ts(8,13): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
e01.ts(11,18): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
e01.ts(15,18): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
e01.ts(19,13): error TS7053: Element implicitly has an 'any' type because expression of type '"grape"' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
Property 'grape' does not exist on type '{ apple: number; banana: number; orange: number; }'.
npx tsc --noEmit --locale ja では次の文面になります。
e01.ts(4,10): error TS7053: 型 'string' の式を使用して型 '{ apple: number; banana: number; orange: number; }' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
型 'string' のパラメーターを持つインデックス シグネチャが型 '{ apple: number; banana: number; orange: number; }' に見つかりませんでした。
e01.ts(19,13): error TS7053: 型 '"grape"' の式を使用して型 '{ apple: number; banana: number; orange: number; }' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
プロパティ 'grape' は型 '{ apple: number; banana: number; orange: number; }' に存在しません。
エラー文の読み方
| 部分 | 意味 |
|---|---|
expression of type 'string' |
[ ] の中に入れた式の型。ここが string なら「キーの型が広い」 |
can't be used to index type '{ ... }' |
アクセスしようとしたオブジェクトの型 |
2 行目 No index signature with a parameter of type 'string' |
そのオブジェクトの型は「任意の文字列キー」を受け付けていない |
2 行目 Property 'grape' does not exist(19 行目) |
キーがリテラルで、その名前のプロパティが無い |
エラーになる場所は 4 種類あります。引数(4 行目)、string と注釈した変数(8 行目)、for...in のループ変数(11 行目)、Object.keys の要素(15 行目)です。どれも [ ] に入れた式の型が string になっている点が共通しています。
なお、const key = "apple" と型注釈なしで書けば、型は "apple" になりエラーは出ません。8 行目のエラーは : string の注釈で型が広がったことが原因です。
このコードは JavaScript としては動きます。node e01.ts の末尾 2 行は次のとおりで、存在しないキーは undefined になります。TS7053 は、この undefined を見落とさないための検査です。
120
undefined
解法1・2:キーの型を狭める(keyof typeof と K extends keyof T)
キーが決まっているオブジェクトでは、受け取る側の型を string から「実在するキーのユニオン」に変えるのが基本です。
// s1.ts
const prices = { apple: 120, banana: 80, orange: 100 };
// 1. keyof typeof:受け取るキーを「実在する 3 つ」に限定する
type Fruit = keyof typeof prices; // "apple" | "banana" | "orange"
function getPrice(name: Fruit) {
return prices[name];
}
console.log(getPrice("apple"));
// 2. K extends keyof T:どのオブジェクトにも使える汎用版
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
console.log(getProp(prices, "banana"));
console.log(getProp({ id: 1, title: "memo" }, "title"));
export {};
120
80
memo
存在しないキーや、string 型の変数を渡すと、呼び出し側で止まります。
// s1e.ts(s1.ts の関数に続けて)
getPrice("grape");
getProp(prices, "grape");
const input: string = "apple";
getPrice(input);
s1e.ts(9,10): error TS2345: Argument of type '"grape"' is not assignable to parameter of type '"apple" | "banana" | "orange"'.
s1e.ts(10,17): error TS2345: Argument of type '"grape"' is not assignable to parameter of type '"apple" | "banana" | "orange"'.
s1e.ts(12,10): error TS2345: Argument of type 'string' is not assignable to parameter of type '"apple" | "banana" | "orange"'.
12 行目のとおり、string 型の値はそのままでは渡せません。フォームの入力値のように実行時に決まる文字列は、解法 5 で確かめてから渡します。
解法3・4:オブジェクトの型を広げる(Record とインデックスシグネチャ)
キーが後から増える辞書なら、オブジェクト側に「任意の文字列キーを受け付ける」と宣言します。
// s3.ts
// 3. Record<string, number>:キーが後から増える辞書
const stock: Record<string, number> = { apple: 5 };
stock.banana = 12;
const name: string = "grape";
const n = stock[name];
console.log(n, typeof n);
console.log(stock[name].toFixed(1));
export {};
tsc はエラー 0 件ですが、実行すると 7 行目で止まります。
undefined undefined
file:///home/taro/app/s3.ts:7
console.log(stock[name].toFixed(1));
^
TypeError: Cannot read properties of undefined (reading 'toFixed')
(パスの表示は環境に合わせて置き換えています。)
Record<string, number> は「どのキーにも number が入っている」という宣言なので、存在しないキーを読んでも型は number のままです。この差を埋める設定が、後述の noUncheckedIndexedAccess です。
インデックスシグネチャは、固定のプロパティと組み合わせたいときに使います。
// s4.ts
// 4. インデックスシグネチャ:固定のプロパティと可変のキーを同居させる
interface Labels {
title: string;
[key: string]: string;
}
const labels: Labels = { title: "一覧", save: "保存" };
const k: string = "cancel";
const v = labels[k];
console.log(v, labels.title);
export {};
undefined 一覧
Record<string, V> と { [key: string]: V } は同じ意味です。辞書だけなら Record、title のような必須プロパティを併記するならインデックスシグネチャと使い分けます。
解法5:in と Object.hasOwn で確かめてから読む
外から来た文字列は、実行時に「キーとして存在するか」を確かめるのが確実です。ただし、in や Object.hasOwn を if に書くだけでは、キーの型は string のままでした。
// s5b.ts
const prices = { apple: 120, banana: 80, orange: 100 };
function a(name: string) {
if (name in prices) {
return prices[name];
}
if (Object.hasOwn(prices, name)) {
return prices[name];
}
}
export { a };
s5b.ts(4,12): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
s5b.ts(7,12): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; banana: number; orange: number; }'.
No index signature with a parameter of type 'string' was found on type '{ apple: number; banana: number; orange: number; }'.
in 演算子が絞り込むのは右側のオブジェクトの型で、左側のキーの型ではないためです。そこで、判定を型述語(key is keyof T)を返す関数にまとめます。
// s5.ts
const prices = { apple: 120, banana: 80, orange: 100 };
// 5. in / Object.hasOwn で確かめてから読む
function isKey<T extends object>(obj: T, key: PropertyKey): key is keyof T {
return Object.hasOwn(obj, key);
}
function show(name: string) {
if (isKey(prices, name)) {
console.log(name, prices[name]);
} else {
console.log(name, "は登録されていません");
}
}
show("apple");
show("grape");
show("toString");
console.log("toString" in prices, Object.hasOwn(prices, "toString"));
export {};
apple 120
grape は登録されていません
toString は登録されていません
true false
最後の行が in と Object.hasOwn の違いです。in は toString のような継承されたプロパティにも true を返します。入力値の判定には、自分自身のプロパティだけを見る Object.hasOwn(ES2022)が合っています。
解法6:as const と satisfies で定数表を作る
ラベルや設定のような定数表は、as const で値まで固定し、satisfies で形を検査します。
// s6.ts
// 6. as const + satisfies:値まで固定し、形も検査する
const STATUS = {
draft: "下書き",
published: "公開中",
archived: "保管",
} as const satisfies Record<string, string>;
type StatusKey = keyof typeof STATUS;
type StatusLabel = (typeof STATUS)[StatusKey];
function label(key: StatusKey): StatusLabel {
return STATUS[key];
}
console.log(label("draft"));
export {};
下書き
StatusKey は "draft" | "published" | "archived"、StatusLabel は "下書き" | "公開中" | "保管" になります。: Record<string, string> と注釈で書くとキーが string に広がってしまうため、定数表では satisfies を使うのがポイントです。
解法7:Map に置き換える
実行時にキーを追加・削除する表は、オブジェクトより Map のほうが型と実際の動きが一致します。
// s7.ts
// 7. Map:実行時に増減するキーと値の表
const stock = new Map<string, number>([["apple", 5]]);
stock.set("banana", 12);
const name: string = "grape";
const n = stock.get(name);
console.log(n);
console.log(stock.get("apple")?.toFixed(1), stock.has("toString"));
export {};
undefined
5.0 false
get の戻り値は number | undefined なので、確認せずに使うと tsc が知らせてくれます。
// s7e.ts
const stock = new Map<string, number>([["apple", 5]]);
console.log(stock.get("grape").toFixed(1));
export {};
s7e.ts(2,13): error TS2532: Object is possibly 'undefined'.
stock.has("toString") が false になる点も、オブジェクトの in との違いです。
解法8:as keyof typeof で断定する
as はキーの型を断定してエラーを消します。実行時の確認は行われないので、存在しないキーでもそのまま進みます。
// s8.ts
const prices = { apple: 120, banana: 80, orange: 100 };
// 8. as keyof typeof:型の検査を通すだけで、実行時の確認はしない
function getPrice(name: string) {
return prices[name as keyof typeof prices];
}
const p = getPrice("grape");
console.log(p);
console.log(typeof getPrice("toString"));
console.log(p.toFixed(1));
export {};
tsc はエラー 0 件で、実行結果は次のとおりです。
undefined
function
file:///home/taro/app/s8.ts:10
console.log(p.toFixed(1));
^
TypeError: Cannot read properties of undefined (reading 'toFixed')
p の型は number ですが、実際の値は undefined です。"toString" を渡すと継承された関数が返っています。as を使うのは、直前で isKey などの確認を済ませている場合に限ると、型と値のずれを防げます。
Object.keys・Object.entries・for...in が string を返す理由
Object.keys(prices) の型は string[] で、("apple" | "banana" | "orange")[] ではありません。これは TypeScript の型が「最低限このプロパティを持つ」という意味で、型に書かれていないプロパティを実行時に持っていてもよいからです。
// h2.ts
// Object.keys が string[] を返す理由:型に無いプロパティが実行時にはありうる
type Point = { x: number; y: number };
function sum(p: Point) {
let total = 0;
for (const k of Object.keys(p) as (keyof Point)[]) {
total += p[k];
}
return total;
}
const p3 = { x: 1, y: 2, label: "A" };
console.log(Object.keys(p3));
console.log(sum(p3));
export {};
tsc はエラー 0 件ですが、実行結果は数値になりません。
[ 'x', 'y', 'label' ]
3A
p3 は x と y を持つので Point として渡せます。しかし実行時の Object.keys は label も返すため、1 + 2 + "A" で "3A" になりました。Object.keys の戻り値を keyof T と断定してよいのは、そのオブジェクトが余分なプロパティを持たないと分かっているときです。
型つきヘルパー(自分で定義した定数に使う)
オブジェクトリテラルで定義した定数のように、中身が確定しているものには次のヘルパーが便利です。
// h1.ts
const prices = { apple: 120, banana: 80, orange: 100 };
// 型つきヘルパー:キーの型を保ったまま取り出す
function typedKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
function typedEntries<T extends object>(obj: T): [keyof T, T[keyof T]][] {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}
for (const k of typedKeys(prices)) {
console.log(k, prices[k]);
}
for (const [k, v] of typedEntries(prices)) {
console.log(k, v * 2);
}
// for...in はループ変数に型注釈を書けないので、先に宣言する
let k: keyof typeof prices;
for (k in prices) {
console.log(k, prices[k]);
}
// 値だけ使うなら Object.values / Object.entries で足りる
console.log(Object.values(prices).reduce((a, b) => a + b, 0));
for (const [name, price] of Object.entries(prices)) {
console.log(name, price.toFixed(0));
}
export {};
apple 120
banana 80
orange 100
apple 240
banana 160
orange 200
apple 120
banana 80
orange 100
300
apple 120
banana 80
orange 100
| やりたいこと | 書き方 | TS7053 |
|---|---|---|
| キーと値の両方を使う | Object.entries(obj) で [name, value] を受け取る |
出ない(obj[k] を書かない) |
| 値だけを使う | Object.values(obj) |
出ない |
キーで obj[k] を読みたい |
typedKeys(obj) か、let k: keyof typeof obj を先に宣言した for...in |
出ない |
Object.keys(obj).forEach((k) => obj[k]) |
そのまま | 出る |
ループの中で obj[k] を書かずに済む Object.entries に直すのが、いちばん手数の少ない直し方です。
noUncheckedIndexedAccess で戻り値の型はどう変わるか
noUncheckedIndexedAccess は、インデックスシグネチャや配列の添字で読んだ値の型に undefined を加える設定です。strict には含まれていないので、個別に有効にします。
// n1.ts
const stock: Record<string, number> = { apple: 5 };
const list = [10, 20, 30];
const fixed = { apple: 120, banana: 80 };
const a = stock["grape"];
const b = list[5];
const c = fixed["apple"];
const d = new Map<string, number>().get("grape");
console.log(a.toFixed(1));
console.log(b.toFixed(1));
console.log(c.toFixed(1));
console.log(d.toFixed(1));
export {};
設定なし(strict のみ):
n1.ts(13,13): error TS18048: 'd' is possibly 'undefined'.
npx tsc --noEmit --noUncheckedIndexedAccess:
n1.ts(10,13): error TS18048: 'a' is possibly 'undefined'.
n1.ts(11,13): error TS18048: 'b' is possibly 'undefined'.
n1.ts(13,13): error TS18048: 'd' is possibly 'undefined'.
| 読み方 | 設定なしの型 | 設定ありの型 |
|---|---|---|
Record<string, number> を stock["grape"] |
number |
number | undefined |
配列を list[5] |
number |
number | undefined |
キー固定のオブジェクトを fixed["apple"] |
number |
number(変わらない) |
Map の get |
number | undefined |
number | undefined(もともと確認が必要) |
解法 3 の s3.ts も、この設定を付けると s3.ts(7,13): error TS2532: Object is possibly 'undefined'. と知らせてくれるようになりました。Record やインデックスシグネチャを使うなら、noUncheckedIndexedAccess を併せて有効にすると、存在しないキーへの備えを型が促してくれます。一方、解法 8 の as は、この設定を付けてもエラーになりませんでした。
TS7053 が出る条件とバージョンによる違い
- TS7053 は
noImplicitAnyが有効なときに出ます(strict に含まれます)。7.0.2 で--noImplicitAny falseを付けると、e01.ts はエラー 0 件になりました。この場合prices[name]は暗黙のanyとして扱われ、以降の型チェックが働かなくなります。 - e01.ts・s1e.ts・n1.ts を 5.9.3/6.0.3/7.0.2 で流したところ、出力は 3 つの版で完全に同じでした。
- 以前あった
suppressImplicitAnyIndexErrorsオプションは使えません。指定したときの出力は次のとおりです。
| 版 | --suppressImplicitAnyIndexErrors を付けた結果 |
|---|---|
| 5.9.3/6.0.3 | error TS5102: Option 'suppressImplicitAnyIndexErrors' has been removed. Please remove it from your configuration. |
| 7.0.2 | error TS5023: Unknown compiler option '--suppressImplicitAnyIndexErrors'. |
どれを選ぶか:判断の流れ
flowchart TD
A{"キーはコードに書かれた固定のもの?"} -->|はい| B{"キーをコード中に直接書く?"}
A -->|"いいえ(実行時に増える)"| G{"追加・削除が多い?"}
B -->|はい| C["解法1 keyof typeof / 解法6 as const + satisfies"]
B -->|"いいえ(入力値など)"| D["解法5 型述語で確かめる"]
G -->|はい| E["解法7 Map"]
G -->|いいえ| F["解法3 Record + noUncheckedIndexedAccess"]
| 状況 | おすすめ | 理由 |
|---|---|---|
| 定数の表から、コードに書いたキーで引く | 1 または 6 | 打ち間違いがコンパイル時に見つかる |
| 汎用の取得関数を作る | 2 | 戻り値の型 T[K] までキーごとに決まる |
| 入力値・クエリ文字列で引く | 5 | 実行時に確かめ、無い場合の処理を書ける |
| ループで全部処理する | Object.entries |
obj[k] を書かずに済む |
| キーが増えていく集計表 | 7(または 3) | undefined の確認を型が促す |
| 直前で確認済みの値を渡す | 8 | 確認と断定が近くにあれば読み手にも伝わる |
よくある質問
Q. (obj as any)[key] で通すのと、何が違いますか?
A. as any は戻り値も any になり、その後の .toFixed() などの打ち間違いも検査されなくなります。解法 8 の key as keyof typeof obj は戻り値の型が保たれるぶん影響が小さく、解法 5 の型述語なら実行時の確認まで行えます。
Q. obj.apple はエラーにならないのに、obj[key] だけエラーになるのはなぜですか?
A. obj.apple はプロパティ名がコードに書かれていて、型にあるかどうかを確かめられるためです。obj[key] は key の型で判断するので、string だと「どのプロパティか分からない」という扱いになります。const key = "apple" のようにリテラル型で推論される変数なら、ブラケット記法でもエラーになりません。
Q. 数値キーや配列でも TS7053 は出ますか?
A. 配列は数値のインデックスシグネチャを持つため、list[i] では出ません。存在しない位置を読んだときの undefined に備えたい場合は、noUncheckedIndexedAccess を有効にすると list[5] の型が number | undefined になります。
Q. enum の値をキーにしたいときはどうしますか?
A. 解法 6 のように as const を付けたオブジェクトを作り、keyof typeof でキーの型を取り出す方法が使えます。キーのユニオン型と値の表が 1 か所にまとまり、ラベルの追加も 1 行で済みます。
まとめ
- TS7053 は「
[ ]に入れたキーの型が広すぎる」ことを知らせるエラー。キーの型を狭めるか、オブジェクトの型を広げて直す - 固定のキーには
keyof typeof、汎用関数にはK extends keyof T。存在しないキーは TS2345 としてコンパイル時に見つかる - 可変のキーには
Record・インデックスシグネチャ・Map。RecordはnoUncheckedIndexedAccessと併用するとundefinedの確認を促してくれる inやObject.hasOwnをifに書くだけではキーは絞り込まれない。key is keyof Tを返す型述語にまとめるObject.keysがstring[]を返すのは、型に無いプロパティが実行時にありうるため。ループはObject.entriesに直すのが近道as keyof typeofは検査を通すだけ。直前で確認済みの値に限って使う- 5.9.3/6.0.3/7.0.2 でエラーの内容は同じ。
noImplicitAnyを無効にすると出なくなるが、その値はanyになる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TS7053 オブジェクトに文字列キーでアクセスすると出る型エラーの直し方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。