How can you create an interface where the value type is determined by the key, but not all keys are predefined?

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,
};

Answer №1

I have come up with a potential solution on the Typescript Playground. Give it a try and see if it solves your problem.

interface Datum {
    total: number;
    covered: number;
    pct: number;
}

// I've avoided using the name File so as not to conflict with the dom definition.
interface FileEntry {
    lines: Datum;
    branches: Datum;
}

type TreeEntryExcludeProps = {
    [P in keyof FileEntry]?: never;
}

type Tree = {
    [key: string]: FileEntry | Tree;
} & TreeEntryExcludeProps;

// No compilation errors here

const a: Tree = {
    folder: {
        "filename.js": {
            lines: {
                total: 0,
                covered: 1,
                pct: 2
            },
            branches: {
                total: 0,
                covered: 1,
                pct: 2
            }
        },
        subfolder: {
            "filename.js": {
                lines: {
                total: 0,
                covered: 1,
                pct: 2
                },
                branches: {
                    total: 0,
                    covered: 1,
                    pct: 2
                }
            }
        }
    }
}

const b: Tree = {
    folder: {
        lines: { // Error lines property is missing total, covered and pct

        }
    }
}

const c: Tree = {
    folder: { // branches property is missing
        lines: { 
            total: 0,
            covered: 1,
            pct: 2
        }
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transmit information from an IONIC 3 application to a PHP server using the POST method

Having trouble sending data from Ionic 3 to php via the http library using post? When php tries to retrieve it, it's unable to locate. Below is the Typescript file that generates the token to be sent to php and calls the php file on localhost (xampp) ...

Mastering Props Typing in React Using TypeScript

Currently, I am faced with the task of defining the following: interface MyCompProps { someAttr: number } Instead of having to explicitly list all the aria-* attributes I need upfront, I would like to simply use aria- and other normal HTML attributes ...

Can you change the method definition of a built-in class using TypeScript?

Working with the Web Audio Api in TypeScript has presented me with a minor issue. I am trying to enhance AudioParam's prototype by adding some convenience methods that can be used on any parameter. However, I keep getting an error from the compiler st ...

Unable to import JSX/TSX component from a separate React application into my primary React application

I find myself facing a challenge with integrating two React applications with different setups. Background: I was assigned the task of developing a design system using ReactJS that would be implemented in their primary application. Despite my limited kn ...

In the process of developing a custom Vue component library with the help of Rollup and VueJS 3

My goal is to develop a custom Vue component library using rollup and Vue.js. The process went smoothly with Vue2, but I encountered issues parsing CSS files with Vue3. To address this, I updated the dependencies in the package.json file. package.json { ...

Activate TypeScript EMCAScript 6 support for Cordova projects in Visual Studio

I am interested in utilizing the async/await feature of TypeScript in my VS2015 Cordova project. I have updated "target": "es6" in tsconfig.json Although there are no errors shown in intellisense, I encounter the following error while building the project ...

The element 'stripe-pricing-table' is not a recognized property of the 'JSX.IntrinsicElements' type

I am currently trying to incorporate a pricing table using information from the Stripe documentation found at this link. However, during the process, I encountered an issue stating: "Property 'stripe-pricing-table' does not exist on type &ap ...

TS2304 TypeScript (TS) Unable to locate the specified name

Encountering an error message stating Cannot find name 'Record'. Do I need to install a specific package for this class? Severity Code Description File Project Line Suppression State Error TS2304 (TS) Cannot find name 'Record ...

There is an implicit 'any' type error binding element 'currency' in Graphql React Typescript

I am facing an issue with my code. I want to use the EXCHANGE_RATES in renderedExchangeRates, but I am receiving an error message saying "Error Message: Binding element 'currency' implicitly has an 'any' type." I understand that this is ...

Vercel is encountering difficulty locating a module or type declaration during the construction of a Next.js application

Currently, I'm facing an issue while trying to deploy a Next.js app to Vercel. The package react-swipeable appears to have its own type declarations but the build fails with the following error: Running "npm run build" ... > next build ... Failed t ...

What is the best way to check the API response status in NextJS13?

Currently, I am experimenting with different methods to handle API HTTP status in my NextJS-13 project but so far nothing has been successful. Note: TypeScript is being used in this project. Below is my code snippet with a static 200 API response and the ...

Transforming an array of flat data into a hierarchical tree structure

I'm facing a challenge with my script. I have an Array of FlatObj and some rules, and I need to create a converter function that transforms them into TreeObj. The Rules are: If an object has a higher depth, it should be a child of an object with a l ...

Tips for effectively managing index positions within a dual ngFor loop in Angular

I'm working on a feedback form that includes multiple questions with the same set of multiple choice answers. Here's how I've set it up: options: string[] = ['Excellent', 'Fair', 'Good', 'Poor']; q ...

The nested fields in PayloadCMS GraphQL are appearing as null

When using the PayloadCMS GraphQL plugin, I encountered an issue with the type: "relationship" fields always returning null, no matter what I tried. My goal is to create a simple blog without any complexities. Despite reaching out for help in the ...

What is the best way to have text wrap around an icon in my React application?

I am facing an issue while trying to display the note description over the trash icon in a React app. I have tried various methods but can't seem to achieve the desired effect. Can anyone guide me on how to get this layout? Here is what I intend to a ...

Tips for updating the icon based on the active or inactive status in ag-grid within an angular application

HTML* <ng-template #actionButtons let-data="data"> <div class="cell-actions"> <a href="javascript:;" (click)="assign()"> <i nz-icon nzType="user-add" nzTheme= ...

Refreshing the private route redirects the user to the login page

Currently, I am working on setting up a private route within my React app. I have integrated Redux and Redux-Toolkit (RTK) Query for handling state management and data fetching. The issue I am facing is that whenever I reload the private page, it redirects ...

Error message in Angular 2 RC-4: "Type 'FormGroup' is not compatible with type 'typeof FormGroup'"

I'm currently facing an issue with Angular 2 forms. I have successfully implemented a few forms in my project, but when trying to add this one, I encounter an error from my Angular CLI: Type 'FormGroup' is not assignable to type 'typeo ...

Utilizing TypeScript Class Inheritance: The Reference to 'super' is Only Allowed in Members of Derived Classes or Object Literal Expressions

We have encountered a scoping issue while using TypeScript classes with inheritance. It seems that TypeScript/JavaScript does not allow us to use 'super' within a promise structure or an enclosed function. The error message we are getting is: Ty ...

Error type inferred by TypeScript

I am currently in the process of learning TypeScript, and I encountered some errors in the code below: Error: Unexpected token. A constructor, method, accessor, or property was expected. [ts] Declaration or statement expected. class duckType{ public ...