What is the best way to depict object key replacements within a Typescript definition?

I currently have these types:

type PossibleKeys = number | string | symbol;
type ValueOf<T extends object> = T[keyof T]; 
type ReplaceKeys<T extends Record<PossibleKeys, any>, U extends Partial<Record<keyof T, PossibleKeys>> = 
  Omit<T, keyof U> & { [P in ValueOf<U>]: T[keyof U] };

... however, while it is partially working, it's throwing the following error:

Type 'U[keyof U]' cannot be assigned to type 'string | number | symbol'.


A simple demo:

interface Item {
  readonly description: string;
  readonly id: string;
}

interface MyInterface {
  readonly id: string;
  readonly propToReplace: number;
  readonly anotherPropToReplace: readonly Item[];
}

type ReplacedUser = ReplaceKeys<MyInterface, { propToReplace: 'total', anotherPropToReplace: 'items' }>;

In the ReplacedUser type, I can see that it is almost correct. The inferred type is:

{ id: string; total: number | readonly Item[]; items: number | readonly Item[]; }

However, I am expecting:

{ id: string; total: number; items: readonly Item[]; }

What am I doing incorrectly? I would like to understand how to specify that P should receive the values passed in U to prevent Typescript errors and then obtain the correct type for a specific value.

Answer №1

To simplify the process, consider reversing the type parameter U for your function ReplaceKeys:

type PossibleKeys = number | string | symbol;
type ReplaceKeys<T extends {}, U extends Record<PossibleKeys, keyof T>> = Omit<T, ValueOf<U>> & {
    [K in keyof U]: T[U[K]]
};

You can implement this as follows:

type ReplacedUser = ReplaceKeys<MyInterface, { total: 'propToReplace', items: 'anotherPropToReplace' }>;

If altering the shape of U is not an option, handling it becomes more challenging:

// Types mentioned earlier in the post
interface Item {
  readonly description: string;
  readonly id: string;
}

interface MyInterface {
  readonly id: string;
  readonly propToReplace: number;
  readonly anotherPropToReplace: readonly Item[];
}

// All possible key types
type PossibleKeys = number | string | symbol;

// Helper type to extract non-nullable values from type T
type DefinedValues<T> = NonNullable<T[keyof T]>;

// Helper type for replacement object - a record with valid keys and matching keys in input type T
// 
// Partial ensures that not all keys of T need to be passed in replacements
type Replacements<T extends {}> = Partial<Record<keyof T, PossibleKeys>>;

// Type to swap object keys for values
type Invert<T extends Replacements<C>, C extends {} = {}> = {
  [N in DefinedValues<T>]: {
    [K in keyof T]: N extends T[K] ? K : never
  }[keyof T]
}

type ReplacedKeys<T extends {}, R extends Replacements<T>> = Omit<T, keyof R | DefinedValues<R>> & {
  [N in keyof Invert<R>]: {
    [L in keyof R]: N extends R[L] ? (L extends keyof T ? T[L] : never) : never;
  }[keyof R]
}

However, be cautious when using the second approach as it may not warn you about duplicate mappings:

type ReplacedUser = ReplacedKeys<MyInterface, { propToReplace: 'total', anotherPropToReplace: 'total' }>;

For testing purposes, visit the playground here.

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

Showing data related to a certain value - what's the best way?

I am currently working on a page where I need to display and edit specific user information at /users/524.json. However, I also want to include the working days from another table on the same page. I have provided most of the code below for reference. Unfo ...

Bring in Event Types from React using TypeScript

Is there a way to import Event Types from React and use them in Material-ui? Specifically, I am looking for guidance on how to import KeyboardEvent so that it can be utilized for onKeyDown callback type annotation. I have examined the .d.ts file of Mater ...

Having trouble with GraphQL Explorer and Express Sessions compatibility?

Struggling to implement a login system using GraphQL and Express, but facing issues with session persistence. Despite logging in, req.session.userId remains undefined. Code snippet: (async () => { await connect(process.env.MONGO_URI!, { dbName: "ex ...

The functionality of the disabled button is malfunctioning in the Angular 6 framework

Just starting out with angular 6 and I'm using formControl instead of FormGroup for business reasons. app.component.html <button class="col-sm-12" [disabled]="comittee_Member_Name.invalid && comittee_Member_Number.invalid && c ...

A mistake has occurred: Unhandled promise rejection TypeError: Unable to assign the property 'devices' to an undefined object in Ionic 4 with Angular

Within my MyDevicesPage class, I am attempting to manipulate the res object and then pass it to the updateDevicesToServer method of DataService for further actions. The code compiles without errors, but at runtime, an error is thrown: ERROR Error: Uncaught ...

The specified reference token grant value of [object Object] could not be located in the store

Currently, I am working with NestJs along with the oidc passport strategy using identityserver. Below is a snippet of the code: import { UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; ...

Executing Cross-Component Communication in Angular 7: A Quick Guide

I've encountered a challenge with a checkbox in the header component. If the checkbox is checked, I need to display an alert message when the application loads. The tricky part is that the checkbox is linked to the home component. Therefore, if I am o ...

The file located at 'node_modules/minimatch/dist/cjs/index' does not contain an exported element called 'IMinimatch'. Perhaps you intended to reference 'Minimatch' instead?

I encountered an error while using rimraf as a devDependency (v5.0.0) in my project. The error message I received was: node_modules/@types/glob/index.d.ts:29:42 - error TS2694: Namespace '".../node_modules/minimatch/dist/cjs/index"' has ...

A function that takes in a type identifier and a portion of a type, and then outputs the full type

I'm currently facing a challenge with TypeScript generics. I have several types (referred to as Animals) that each have a unique attribute, "type." Additionally, I have a function called createAnimal which takes the type of animal and a partial object ...

Exploring the Behavior of Typescript Modules

When working with the module foo, calling bar.factoryMethod('Blue') will result in an instance of WidgetBlue. module foo { export class bar { factoryMethod(classname: string): WidgetBase { return new foo["Widget" + classname](); ...

Pass the type of object property as an argument in the function

I've been having trouble trying to figure this out and haven't been able to find a solution in the TS docs or examples I came across. Essentially, I'm working with a configuration specifying operations on object properties and looking to en ...

How can I set up multiple queues in NestJs Bull?

My goal is to set up multiple queues in NestJs, and according to the documentation: You can create multiple queues by providing multiple comma-separated configuration objects to the registerQueue() method. However, I am encountering an issue where VSco ...

Adding elements to a page while it is running can be achieved using a variety

Working on a college project, I am developing a demo web-app in Angular. The goal is to implement a feature where clicking a button adds a new node to the DOM tree. In JavaScript, a simple solution would be: document.getElementById('ticket-container& ...

Steps for injecting strings directly into Angular2 EventBindingWould you like to learn how

Is it feasible to achieve something similar to this? <li><a href="#" target="_blank" (click)="createComponent(MYSTRINGHERE)">Departamentos</a></li> ...

An issue has occurred: the function cannot be applied to the intermediate value that is currently being processed

I am currently working on an Angular 5 CRUD application, utilizing Google Firebase services. I have been following a helpful video tutorial on YouTube (link here), but I encountered this error ngOnInit() { var x = this.employeeService.getData(); x.sna ...

Creating an Inner Join Query Using TypeORM's QueryBuilder: A Step-by-Step Guide

Hello there! I'm new to TypeORM and haven't had much experience with ORM. I'm finding it a bit challenging to grasp the documentation and examples available online. My main goal is to utilize the TypeORM QueryBuilder in order to create this ...

Guide to incorporating code coverage in React/NextJs using Typescript (create-next-app) and cypress

I initiated a fresh project using create-next-app with the default settings. npx <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="365544535742531b58534e421b57464676070518021801">[email protected]</a> --- Would you l ...

How can I update a dropdown menu depending on the selection made in another dropdown using Angular

I am trying to dynamically change the options in one dropdown based on the selection made in another dropdown. ts.file Countries: Array<any> = [ { name: '1st of the month', states: [ {name: '16th of the month&apos ...

Is there a way for me to change the value and placeholder attributes on the Clerk's SignIn component?

Within Clerk's documentation, there is guidance on accessing the input field using the appearance prop as demonstrated below: <SignIn appearance={{ elements: { formFieldInput: 'bg-zinc-300/30' } }}/& ...

Issue TS1192: The module named "A.module" does not contain a default export

After creating a new module 'A', I attempted to import it in another module named 'B'. However, during compilation, I encountered the following error: Error TS1192: Module '" A.module"' has no default export I wou ...