Typescript: Transforming generic types into concrete types

I am utilizing a Generic type

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  },
};

The purpose of the Generic type is to assist in constructing / validating a new object that I have created.

const NewObject: GenericType = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

The implementation works as expected. However, when attempting to use the new object, VSCode / TypeScript does not display the keys or properties of NewObject unless I remove GenericType from it. Alternatively, I could "extend" the type but that would lead to redundant code.

NewObject.???

Is there a way to retain the functionality of GenericType while also accessing the specific properties of the new object derived from it?


Update 1

I anticipate that VSCode / TypeScript will show / validate

NewObject.key1.prop1
NewObject.key2.prop1

and generate an error for

NewObject.key1.prop2
NewObject.key2.prop3
NewObject.key2.prop321

Answer №1

The concept behind this is to allow GenericTypes to have any string as a key while still enforcing specific value types for those keys at the declaration point.

To achieve this, the Record type can be used to limit the allowed keys of Obj1 to only the specified ones.

type GenericType<K extends string> = Record<K, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}>

When defining Obj1, you can define the allowed keys by setting a union of keys as the first type parameter.

const Obj1: GenericType<"key1" | "key2"> = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

This approach allows TypeScript to provide full type safety when accessing both key1 and key2.

Obj1.key1
// (property) key1: {
//     prop1: string;
//     prop2?: string | undefined;
//     prop3?: number | undefined;
// }

EDIT

Following the OP's preference, rather than specifying all key names or checking optional fields manually, here is an alternative method that ensures the declared object conforms to the constraints of the GenericType interface.

Firstly, a utility type is needed:

type Constraint<T> = T extends Record<string, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}> ? T : never

It will return `never` if `T` does not meet the constraint, otherwise it will return `T` itself.

Next, declare the plain object without type annotations:

const CorrectObj = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

Then assign this object literal to another variable, ensuring the new variable is of type

Constraint<typeof CorrectObj>

const CheckedObj: Constraint<typeof CorrectObj> = CorrectObj

If `CorrectObj` fits the constraint, `CheckedObj` will just be a copy with all fields accessible. However, if the literal does not match the constraints, assigning `CheckedBadObj` to it will result in a type error:

const BadObj = {
  key1: {
    progfdgp1: "hi",
  },
  key2: {
    prop1: "bye",
    prdfgop2: "sup",
  },
};

const CheckedBadObj: Constraint<typeof BadObj> = BadObj
//    ^^^^^^^^^^^^^
// Type '{ key1: { progfdgp1: string; }; key2: { prop1: string; prdfgop2: string; }; }' is not assignable to type 'never'. (2322)

The reason being that when `Constraint<T>` fails, it returns `never`, causing a conflict when trying to assign a non-never value to `CheckedBadObj`.

Although there is some redundancy in declaring two instances of each object literal, this method is necessary for having precise knowledge of the object's fields, including nested objects, while verifying their values against set constraints.

Feel free to experiment with this technique in the playground.

Answer №2

If you want key1 and key2 to appear in the autocomplete menu for Obj1, while still being able to add more keys later, consider this solution:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const generify = <T extends GenericType>(obj: T): T & GenericType => obj;

const Obj1 = generify({
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
});

Currently, I don't have a simpler solution that would provide Obj1 with the same intersection type involving GenericType and a specific type containing only key1 and key2 properties.

Answer №3

Implement a versatile utility function named enforce that:

  • checks if the object adheres to the structure of GenericType using a generic constraint (extends)
  • and infers the type of the passed object.

Sample Code:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

Demonstration:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

const SampleObj1 = enforce({
  key1: {
    prop1: "hello",
  },
  key2: {
    prop1: "goodbye",
    prop2: "see you",
  },
});

SampleObj1.key1.prop1; // Accepted
SampleObj1.key2.prop1; // Accepted

/** 
 * ERROR: Properties do not match the input object
 */
SampleObj1.key1.prop2 // Error 
SampleObj1.key2.prop3 // Error
SampleObj1.key2.prop321 // Error

SampleObj1.key3; // Error 

/**
 * ERRORS: Structure does not align with GenericType
 */
const SampleObj2 = enforce({
  key1: { // Error 
  }
});
const SampleObj3 = enforce({
  key1: {
    prop1: 123, // Error
  }
});

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

``Using backticks to denote HTML syntax - Leveraging Google Charts to create

Has anyone found a way to incorporate HTML in ticks within a Google chart? I am attempting to insert a weather icon from This is my current attempt: const dailyData = new google.visualization.DataTable(); dailyData.addColumn('timeofday' ...

having difficulties sorting a react table

This is the complete component code snippet: import { ColumnDef, flexRender, SortingState, useReactTable, getCoreRowModel, } from "@tanstack/react-table"; import { useIntersectionObserver } from "@/hooks"; import { Box, Fl ...

Parsing error occurred: Unexpected empty character found while attempting to load .lottie files

I have a NextJS application and I'm integrating the dotLottie player from this repository. Even though I've followed the setup instructions provided in the documentation, I keep encountering an error when the component attempts to load the dotLot ...

Angular's HttpClient makes sure to wait for the HTTP requests to complete

Initializing arrays with the call this.Reload.All() is causing confusion and breaking the service due to multiple asynchronous calls. I am looking for a synchronous solution where each call waits for its response before proceeding to the next one. How can ...

Creating objects in Angular 2 through HTTP GET calls

Recently, I've delved into learning Angular 2. My current challenge involves making http get requests to retrieve data and then constructing objects from that data for later display using templates. If you believe my approach is incorrect, please feel ...

Tips for fixing the issue of 'expect(onClick).toHaveBeenCalled();' error

Having trouble testing the click on 2 subcomponents within my React component. Does anyone have a solution? <Container> <Checkbox data-testid='Checkbox' checked={checked} disabled={disabled} onClick={handl ...

Tips for implementing a decorator in a TypeScript-dependent Node module with Create-React-App

I am working on a project using TypeScript and React, which has a dependency on another local TypeScript based project. Here are the configurations: tsconfig.json of the React project: "compilerOptions": { "target": "esnext& ...

New post: "Exploring the latest features in Angular

Looking for help with integrating Angular and SpringREST to fetch data from the backend? Here's my situation: I need to retrieve a JSON string from the backend using a POST request, send it to my site's hosted link, and display it on the user int ...

Passing a function as a prop in a child component and invoking it in React using TypeScript

I have a function that I need to pass to a child component in order to manage the state in the parent component. The function takes an object declared in FriendListItem and adds it to an array as a new object. Despite my research efforts, I am struggling t ...

Will a JavaScript source map file continue to function properly even after the source code file has been minified?

My experience I specialize in utilizing TypeScript and Visual Studio to transform highly organized code into functional JavaScript. My skills involve configuring the project and Visual Studio to perform various tasks: Merging multiple compiled JavaScrip ...

I'm looking to learn how to implement the delete method in an API using TypeScript. Can anyone help me out

I am seeking guidance on utilizing 'axios' within 'nuxt.js'. I have experimented with sample data, and I am particularly interested in learning how to utilize the 'axios' method within 'nuxt.js' using TypeScript. T ...

import types dynamically in TypeScript

One of the files I have is called MyFactory.ts. Here is its content: export type CommandFactory = () => string[] | undefined; export enum FactoryIds {commandFactory : 'commandFactory'} Now, my goal is to dynamically import this file into anot ...

The directive for angular digits only may still permit certain characters to be entered

During my exploration of implementing a digits-only directive, I came across a solution similar to my own on the internet: import { Directive, ElementRef, HostListener } from '@angular/core'; @Directive({ selector: '[appOnlyDigits]' ...

In Angular 16, allow only the row that corresponds to the clicked EDIT button to remain enabled, while disabling

Exploring Angular and seeking guidance on a specific task. I currently have a table structured like this: https://i.stack.imgur.com/0u5GX.png This code is used to populate the table: <tbody> <tr *ngFor="let cus of customers;" [ngClass ...

Troubleshooting Generic Problems in Fastify with TypeScript

I am currently in the process of creating a REST API using Fastify, and I have encountered a TypeScript error that is causing some trouble: An incompatible type error has occurred while trying to add a handler for the 'generateQrCode' route. The ...

Encountering an Issue with Dynamic Imports in Cypress Tests Using Typescript: Error Loading Chunk 1

I've been experimenting with dynamic imports in my Cypress tests, for example using inputModule = await import('../../__tests__/testCases/baseInput'); However, I encountered an issue with the following error message: ChunkLoadError: Loading ...

When I delete the initial element from the array, the thumbnail image disappears

Using react-dropzone, I am attempting to implement image drag and drop functionality. The dropped image is stored in the React state within a files array. However, a problem arises when removing an image from the array causing the thumbnails of the remain ...

Angular 8 and Bootstrap 4 Integration: Navbar Functionality Working, but Issue with Auto-Closing on Click Action (Both Inside and Outside Navbar)

While using ng-bootstrap with Angular 8, I encountered a problem with the navbar. The navbar functions properly by being responsive and opening/closing when clicking the hamburger icon. However, the issue arises when it does not automatically close when a ...

Converting a file buffer to upload to Google Cloud Storage

I have been facing an issue while attempting to upload a file to Google Cloud using a buffer and the save function. The problem I am encountering is that even though the files are uploaded to Google Cloud, they are not in the correct format. When I try to ...

I find it confusing how certain styles are applied, while others are not

Working on my portfolio website and almost done, but running into issues with Tailwind CSS. Applied styling works mostly, but some disappear at certain breakpoints without explanation. It's mainly affecting overflow effects, hover states, and list sty ...