In TypeScript I have:
type Leaf = string[];
type Branch = { [id: string]: Branch | Leaf };
let someBranch:Branch;
let leafOrBranch:Branch|Leaf = someBranch.someSubBranch.someSubSubBranch.someLeafOrBranch;
This gives me the following error (which makes sense):
Property 'someSubSubBranch' does not exist on type 'Branch | Leaf'.
Property 'someSubSubBranch' does not exist on type 'Leaf'.ts(2339)
I could do
let someLeafOrBranch:Branch|Leaf = ((someBranch.someSubBranch as Branch).someSubSubBranch as Branch).someLeafOrBranch;
…but that becomes annoying pretty fast.
Is there a way to change the types so that I can just call someBranch.someSubBranch.someSubSubBranch.someLeafOrBranch
without giving any errors?
5
If you’re certain that the initial branch has the structure that you expect, you can type it that way and the compiler will emit no corresponding diagnostic:
TS Playground
declare const someBranch: {
someSubBranch: {
someSubSubBranch: {
someLeafOrBranch: Branch | Leaf;
};
};
};
const leafOrBranch = someBranch.someSubBranch.someSubSubBranch.someLeafOrBranch;
// ^? const leafOrBranch: Branch | Leaf
However, if you’re not certain about the initial branch structure, then using a series of type assertions will only suppress helpful compiler diagnostics about syntax that would potentially cause a runtime exception.
Alternatively, you can use a function to iteratively index into your branch by providing an array of property accessors which correspond to the nested branch/leaf, and validate the structure along the way, like this:
TS Playground
function getNested(
branch: Branch,
propertyAccessors: readonly (keyof Branch)[],
): Branch | Leaf {
let result = branch as Branch | Leaf;
for (const prop of propertyAccessors) {
if (!Array.isArray(result)) {
const child = result[prop];
if (typeof child === "object") {
result = child;
continue;
}
}
throw new Error(`Invalid porperty accessor: "${prop}"`);
}
return result;
}
declare const someBranch: Branch;
const leafOrBranch = getNested(someBranch, [
"someSubBranch",
"someSubSubBranch",
"someLeafOrBranch",
]);
It should be giving error if you want type safety, so if you just want to avoid it you can just config to ignore it.
If you do want type safety you should have some checks, you can use something like:
function isBranch(value: unknown): value is Branch {
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
}
if (isBranch(someBranch.someSubBranch) && isBranch(someBranch.someSubBranch.someSubSubBranch)) {
let leafOrBranch: Branch | Leaf = someBranch.someSubBranch.someSubSubBranch.someLeafOrBranch;
}
It is still annoying, but it’s better than using as
since it just ignore type check.