Let's say that there is a type:
interface ObjWithKnownKeys {
prop: string;
}
And I need an object that contains objects of this type. I know that I can type it like this:
interface ObjWithUnknownKeysA {
[key: string]: ObjWithKnownKeys;
}
const a: ObjWithUnknownKeysA = {};
// ❌ no error that 'someName' is possibly undefined
a.someName.prop;
for (const key in a) {
const entry = a[key];
// ✔️ no errors
entry.prop;
}
for (const [key, entry] of Object.entries(a)) {
// ✔️ no errors
entry.prop;
}
Or like this:
interface ObjWithUnknownKeysB {
[key: string]: ObjWithKnownKeys | undefined;
}
const b: ObjWithUnknownKeysB = {};
// ✔️ error that 'someName' is possibly undefined
b.someName.prop;
for (const key in b) {
const entry = b[key];
// ❌ error that entry is possibly undefined (but it's clear it's not for given key)
entry.prop;
}
for (const [key, entry] of Object.entries(b)) {
// ❌ error that entry is possibly undefined
entry.prop;
}
But how should I type it to make sure that it will always work as expected?
EDIT AFTER AUTO CLOSE: There is no mention of --noUncheckedIndexedAccess flag in that thread that was marked as similar. I think this flag is a better answer.
CodePudding user response:
This is one of the pain points in TypeScript. By default, the compiler treats types with index signatures as if every possible property of the relevant key type is present and defined. This is convenient since you don't have to convince the compiler that a property is actually defined before using it:
interface StringIndex {
[k: string]: string;
}
const str: StringIndex = { abc: "hello" };
str.abc.toUpperCase(); // okay
And you can easily iterate over keys/values:
for (const k in str) {
str[k].toUpperCase(); // okay
}
for (const v of Object.values(str)) {
v.toUpperCase(); // okay
}
// arrays have numeric index signatures
const arr: Array<string> = ["x", "y", "z"];
for (const s of arr) {
s.toUpperCase(); // okay
}
Unfortunately, it's demonstrably unsafe:
str.boop.toUpperCase(); // no compiler error!