Why am I unable to apply the keyof operator from one type to another type, even though both types have identical keys defined but different value types?

Consider this code snippet. I am encountering a TypeScript error specifically on the last compat[k] line with the following error message:

Type 'keyof T' cannot be used to index type 'Partial<CompatType>'

export type KeysOfType<T, U, B = false> = {
  [P in keyof T]: B extends true
    ? T[P] extends U
      ? U extends T[P]
        ? P
        : never
      : never
    : T[P] extends U
    ? P
    : never;
}[keyof T];

export type BigIntKeys<T> = KeysOfType<T, bigint, true>

export type CompatType<T> = Omit<T, BigIntKeys<T>> &
  {
    [Property in BigIntKeys<T>]: string;
  };

export function compatModel<T>(model: T): CompatType<T> {
  const compat: Partial<CompatType<T>> = {};
  for (const k of Object.keys(model) as Array<keyof T>) {
    const v = model[k];
    compat[k] = typeof v === "bigint" ? v.toString() : v;
  }
  return compat as CompatType<T>;
};

The keys of both types should align completely, but there is a discrepancy in the type of values within the objects. This inconsistency should allow me to use the keys of one type to access the other, however, it does not seem to work that way. Am I missing something or making a mistake somewhere?

TS Playground

Answer №1

One of the limitations of TypeScript lies in its design; refer to microsoft/TypeScript#28884. As mentioned in this comment, "complementary subsets of a higher order type, constructed using Pick<T, K> or by other means, is not assignable back to that higher order type."

For instance, a type like

Omit<T, K> & Record<K, string>
where K extends keyof T will not be recognized as having the same keys as T, even though logically it should. This discrepancy occurs because the compiler fails to equate
Exclude<keyof T, K> | Extract<keyof T, K>
with keyof T when T and/or K are unspecified generic types:

function foo<T, K extends keyof T>(a: keyof T) {
  const b: Extract<keyof T, K> | Exclude<keyof T, K> = a; // error!
}

In situations where the exact types for T and K are unknown, the compiler defers the evaluation of

Extract<keyof T, K> | Exclude<keyof T, K>
, hence failing to recognize their equivalence to keyof T.


To address this issue, you can construct CompatType as a homomorphic mapped type directly from T, employing a conditional type to determine whether the specific key K from keyof T should belong to BigIntKeys<T> and selecting the value type accordingly:

type CompatType<T> = { [K in keyof T]: 
  T[K] extends bigint ? bigint extends T[K] ? string : T[K] : T[K] 
}

This approach results in more visually appealing types,

type Check = CompatType<{ a: string, b: bigint, c: number, d: boolean }>;
/* type Check = {
    a: string;
    b: string;
    c: number;
    d: boolean;
} */

Furthermore, the compiler acknowledges that CompatType<T> indeed shares the same keys as T, even in scenarios involving generic types for T:

export function compatModel<T>(model: T): CompatType<T> {
  const compat: Partial<CompatType<T>> = {};
  for (const k of Object.keys(model) as Array<keyof T>) {
    const v = model[k];

    compat[k]; // no error here

    compat[k] = typeof v === "bigint" ? v.toString() : v; // still error here, unrelated
  }
  return compat as CompatType<T>;
};

Nevertheless, an error may surface while attempting to assign

typeof v === "bigint" ? v.toString() : v
to compat[k] due to the compiler's inability to validate if something is assignable to a conditional type.


In situations where you are confident in the correctness of your code despite the compiler's doubts, you have the option of utilizing type assertions or resorting to the any type to relax type checking enough to appease the compiler:

export function compatModel<T>(model: T): CompatType<T> {
  const compat: Partial<Record<keyof T, any>> = {};
  for (const k of Object.keys(model) as Array<keyof T>) {
    const v = model[k];
    compat[k] = typeof v === "bigint" ? v.toString() : v;
  }
  return compat as CompatType<T>;
};

Here, we inform the compiler to ignore concerns regarding the property value types of compat and ultimately return it as CompatType<T>. As long as you are absolutely certain about the accuracy of the typings, this practice is acceptable. However, exercising caution is advised:

const hmm = compatModel({ a: Math.random() < 10 ? 3n : 3 });
hmm.a // number | bigint
if (typeof hmm.a !== "number") {
  3n * hmm.a; // no compile time error, but runtime issue: "cannot convert BigInt to number" 
}

The a property is inferred as number | bigint, resulting in the compiler assuming that hmm.a might hold a bigint, which is impossible. While there exist remedies to such dilemmas, these fall beyond the scope of the current discussion length.

Please be mindful when resorting to type assertions or any to mitigate compiler errors, as they increase the likelihood of potentially critical errors slipping through unnoticed.

Link to Playground with Code Example

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

Adding connected types to a list using Typescript

Question regarding Typescript fundamentals. In my code, I have a list that combines two types using the & operator. Here is how it's initialized: let objects: (Object & number)[] = []; I'm unsure how to add values to this list. I attem ...

Display a free Admob banner within an Ionic 3 application

I have integrated Admob's banner into my Ionic 3 app following the guidelines provided in the Ionic documentation at this link. Below is the code snippet I used for displaying the banner on the homepage: import { Component } from '@angular/core ...

Encountering a type error in Typescript when assigning a transition component to a Material UI Snackbar

Attempting to implement snackbar alert messages using Material UI in a React JS application with TypeScript. Encountering a type error when trying to modify the transition direction of the snackbar. Referenced the snackbar demo from Material UI documentat ...

Customizing the placeholder text for each mat input within a formArray

I have a specific scenario in my mat-table where I need to display three rows with different placeholder text in each row's column. For example, test1, test2, and test3. What would be the most efficient way to achieve this? Code Example: <div form ...

Is there a way to specify object keys in alignment with a specific pattern that allows for a variety of different combinations

I am seeking a way to restrict an object to only contain keys that adhere to a specific pattern. The pattern I require is: "{integer}a+{integer}c". An example of how it would be structured is as follows: { "2a+1c": { // ... } } Is there a ...

The file that is currently being downloaded has the .pptx extension, but it is being

Take a look at this code snippet: const generateDownload = ({ link, data, title, settings })=> { const newLink = document.createElement('a'); const blobUrl = link || URL.createObjectURL(new Blob([data], settings)); newLink.setAt ...

TypeScript does not throw a compiler error for incorrect type usage

In my current setup using Ionic 3 (Angular 5), I have noticed that specifying the type of a variable or function doesn't seem to have any impact on the functionality. It behaves just like it would in plain JavaScript, with no errors being generated. I ...

Modifying the user interface (UI) through the storage of data in a class variable has proven to be

If I need to update my UI, I can directly pass the data like this: Using HTML Template <li *ngFor="let post of posts; let i = index;"> {{i+1}}) {{post.name}} <button (click)="editCategory(post)" class="btn btn-danger btn-sm">Edit</butto ...

Having trouble extracting parameters with TypeScript in React Router even when they are present

I am in the process of migrating an older project to utilize react and react-router. Additionally, I am fairly new to typescript, which is the language used for this particular project. Any guidance or explanations on these topics would be highly beneficia ...

Fastify Typescript: dealing with an unidentified body

I'm new to Fastify and I've encountered a problem with accessing values in the body using Typescript. Does anyone have any ideas or suggestions? Thanks! Update: I want to simplify my code and avoid using app.get(...) Here's my code snippet ...

Assessing the validity of a boolean condition as either true or false while iterating through a for loop

I am facing an issue that involves converting a boolean value to true or false if a string contains the word "unlimited". Additionally, I am adding a subscription to a set of values and need to use *NgIf to control page rendering based on this boolean. &l ...

Encountering an error when attempting to access undefined property while using a method as a callback

Exploring OOP and angular is new to me. I am currently trying to implement a reusable table with pagination that triggers an API request when the page changes (pagination within the table component). The issue arises when I attempt to access my method usi ...

What could be causing the global npm package to not be utilized for module dependencies?

My typescript and ts-node are already installed globally. In my package.json file, I have the necessary configurations to run tests with npm test. Everything works fine since ts-node and typescript are installed as local modules. { "name": "two_sum", ...

Encountering an issue with NextJS 13 when utilizing the vectorstore recommended by Langchain, specifically receiving an error message stating that HNSWLib

Currently, I am building an application utilizing Langchain and OpenAI for assistance. My approach involves loading data using JSONLoader and intending to save it in a vectorstore. This way, I can provide specific answers to user queries based on the store ...

Guidelines for forming a composite type with elements?

Imagine having a convenient function that wraps a generic component with a specified constant. function wrapComponent(ComponentVariant: ComponentVariantType) { return ( <Wrapper> <ComponentVariant> <InnerComponent /> ...

Refresh PrimeNG dataTable without reloading the table

Currently, I am implementing the functionality of adding new rows to a dataTable in my template. Here is the code snippet from the component: rows: any = {} newrow: any = {} addNewRow(key: string) { let rows = {...this.rows} let newrow = {_key: Math ...

The error message "Unable to find 'encoding'" in NextJS is triggered by the use of try/require in the node_modules folder

Running a NextJS app in typescript with version 13.4.19, utilizing @apollo/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0d7e687f7b687f4d392334233e">[email protected]</a> triggers a warning during the build proce ...

Can someone explain how this "const s: string = ['a'][1];" is considered a valid Typescript expression?

I encountered an unexpected result when I executed the code const s: string = ['a'][1];. Instead of receiving a type error from the Typescript compiler, it returned undefined. This was surprising to me because I believed I was trying to assign an ...

You can easily search and select multiple items from a list using checkboxes with Angular and TypeScript's mat elements

I am in need of a specific feature: An input box for text entry along with a multi-select option using checkboxes all in one place. Unfortunately, I have been unable to find any references or resources for implementing these options using the Angular Mat ...

Arranging Data in Arrays using Angular 4 GroupBy Feature

I'm working with an array structured like this: var data = [ { student: "sam", English: 80, Std: 8 }, { student: "sam", Maths: 80, Std: 8 }, { student: "john", English: 80, Std: 8 }, { student: "j ...