Below is an illustration of a data structure that I aim to define as a type in TypeScript:
const dataExample = {
Folder: {
"Filename.js": {
lines: {
total: 100,
covered: 80,
pct: 80,
},
branches {
total: 100,
covered: 80,
pct: 80,
},
},
Subfolder: {
"Filename.js": {
lines: {
total: 100,
covered: 80,
pct: 80,
},
branches {
total: 100,
covered: 80,
pct: 80,
},
},
},
}
To conform with TypeScript, it seems necessary to describe it in the following manner:
interface Datum {
total: number;
covered: number;
pct: number;
}
interface Tree {
[key: string]: number | Datum | Tree;
}
Is there a way to define it so TypeScript can comprehend that the values on keys lines
and branch
always have the type Datum
, the values on keys total
, covered
, and pct
constantly have the type number
, and any other key's value has the type Tree
? It appears unlikely.
The type definition provided above doesn't offer much help. Whenever referencing a key like
dataExample.Folder.Filename1.line
and attempting to use it, a TypeScript error arises:
Element implicitly has an 'any' type because expression of type '[any type]' cannot be used to index type '[Tree | Datum | number]'. No index signature with a parameter of type '[any type]' was found on type 'Tree'.
To resolve this error, one must coerce the type, essentially defeating the purpose of employing a type initially.
Edit:
Sherif Elmetainy's solution functions flawlessly, albeit with one alteration:
interface Datum {
total: number;
covered: number;
pct: number;
}
// Avoiding using the name File because it's already defined in dom
interface FileEntry {
lines: Datum;
branches: Datum;
}
type TreeEntryExcludeProps = {
// Incorporate properties lines and branches and assign them a type of never.
// This will disallow lines and branches within the type
// The ? makes them optional because otherwise the compiler would point out their absence
[P in keyof FileEntry]?: never;
};
interface Tree extends TreeEntryExcludeProps { // Use an interface and extend it, as opposed to utilizing a union
[key: string]: FileEntry | Tree;
};
// Compiler won't raise any complaints here
In conclusion, I implemented the following:
interface KarmaCoverageDatum {
total: number;
covered: number;
pct: number;
}
interface Datum {
total: number;
covered: number;
skipped: number;
pct: number;
}
interface CoverageDatum {
excluded: number
fileCount: number;
path: string;
lines: KarmaCoverageDatum;
branches: KarmaCoverageDatum;
}
interface CoverageTree {
[key: string]: CoverageDatum | CoverageTree | Datum | number | string;
};
const getTypedDataFromTree = {
coverageNumbersByKey: (key: 'lines' | 'branches', tree: CoverageTree) =>
tree[key] as Datum,
path: (tree: CoverageTree) => tree['path'] as string,
fileCount: (tree: CoverageTree) => tree['fileCount'] as number,
excluded: (tree: CoverageTree) => tree['excluded'] as number,
childTree: (key: string, tree: CoverageTree) => tree[key] as CoverageTree,
};