What causes the loss of type inference for the object literal in this mapped type?

Although contrived, I usually pass an object literal to a function and capture the values of the literal in a generic format, like this:

type Values<V> = {
  a: V;
  b: V;
};

function mapValues<V>(v: Values<V>): V {
  return v as any; // ignore
}
const vn = mapValues({ a: 1, b: 2 }); // inferred number
const vs = mapValues({ a: '1', b: '2' }); // inferred string

Both vn and vs are correctly inferred as number or string based on what I passed to mapValues.

This method even works for indexed types:

function mapValues2<V>(v: { [key: string]: V }): V {
  return v as any;
}
const v2n = mapValues2({ a: 1, b: 2 }); // inferred number
const v2s = mapValues2({ a: '1', b: '2' }); // inferred string

The object literal v correctly identifies its values as string/number (respectively), allowing me to capture that inferred V and use it in the return type.

However, when I introduce a mapped type, like so:

enum Foo {
  a,
  b,
}
function mapValues3<K, V>(o: K, v: { [key in keyof K]: V }): V {
  return v as any;
}
const v3n = mapValues3(Foo, { a: 1, b: 2 }); // inferred unknown
const v3s = mapValues3(Foo, { a: '1', b: '2' }); // inferred unknown

It seems like V loses its ability to determine whether it was string/number (respectively), resulting in an inferred value of unknown.

If I explicitly specify const v3n: number, then it works as expected, but I prefer to rely on type inference to determine V for me.

I'm puzzled as to why switching from [key: string] in the second snippet to [key in keyof K] in the third snippet affects the inference of the : V side of the object literal's type inference.

Any insights or ideas on this matter?

Answer №1

While I can't provide a definitive answer as to why this situation fails, my gut feeling is that the issue lies in attempting to have the function mapValue3(o, v) infer both the keys and values of the v argument based on the keys of the o argument. This seems to lead to an inference of V that occurs too late, causing the compiler to give up and default to a general unknown inference.

There are instances where you have a value val of type

U</code and you're trying to infer a related type <code>T</code from it by treating <code>U
as F<T> for a type function F. This inference may not always be possible for the compiler, even if it seems logical to you. In such cases, my suggestion is to shift focus from inferring
T</code from a value of type <code>U</code to directly inferring <code>U
. Then, represent T as G<U> for a type function G, where G is the inverse of
F</code. Though writing the inverse type function <code>G</code might be more complex than <code>F</code, human intervention is often more effective than relying on the compiler. Make sure to constrain your <code>U
types as
F<any></code for the mapping to work.</p>

<p>This approach also applies to scenarios involving multiple type variables <code>v1
, v2, v3 with types U1 = F1<T1, T2, T3>, U2 = F2<T1, T2, T3>,
U3 = F3<T1, T2, T3></code where you aim to infer <code>T1
, T2, and
T3</code. In such cases, determine <code>G1
, G2, and
G3</code matching <code>T1 = G1<U1, U2, U3>
, T2 = G2<U1, U2, U3>, and
T3 = G3<U1, U2, U3></code respectively, and calculate them directly).</p>

<p>Let's apply this to your function, with a revised structure:</p>

<pre><code>function mapValues3<O, T>(o: O, v: F<O, T>): T {
  return null!
}
type F<O, T> = Record<keyof O, T>; // equivalent to { [key in keyof O]: T };

We aim to transform it to:

function mapValues3<O, U extends F<O, any>>(o: O, v: U): G<O, U> {
  return null!
}
type G<O, U> = ???;

Since the first parameter "O" already involves inferring the value of the parameter, there's no adjustment needed for O. The key now is defining G. How do we extract T out when we have a value of type

U</code as <code>Record<keyof O, T></code? The solution lies in using a lookup type:</p>

<pre><code>type G<O, U> = U[keyof O];

Confirming: G<O, Record<keyof O, T> equals

Record<keyof O, T>[keyof O]
, which simplifies to
{[K in keyof O]: T}[keyof O]</code, resulting in <code>T
. With that, we can eliminate F and
G</code and present you with:</p>

<pre><code>function mapValues3<O, U extends Record<keyof O, any>>(o: O, v: U): U[keyof O] {
  return null!
}

Let's test it:

const v3n = mapValues3(Foo, { a: 1, b: 2 }); // number
const v3s = mapValues3(Foo, { a: "1", b: "2" }); // string

These tests align with your expectations. Let's explore potential edge cases:

const constraintViolation = mapValues3(Foo, { a: "hey" }); // error! "b" is missing

This error is justified, as the second parameter should contain all keys from the first parameter. Additionally:

const excessProp = mapValues3(Foo, { a: 1, b: 2, c: false }); // number, no error

While this result may be acceptable since it doesn't violate constraints, it's essential to note that TypeScript permits extra properties in certain contexts. The inference remains as numbernumber | boolean


Hopefully, this provides some clarity. Best of luck!

Link to the revised code

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

Pattern matching for validating multiple email addresses

I need assistance with validating multiple email inputs using regex in Angular. I am looking to enforce a specific format for the emails, such as: Examples: *****@zigurat.com *****@test.com *****@partlastic.com The ***** can be any characters, but the ...

Angular4 Error: Unable to link to 'ngClass' as it is not recognized as a property of 'input'

Currently, I am facing an issue in my project where I am utilizing lazy loading. Specifically, within my registration module, I am attempting to utilize the [ngClass] directive to append an 'invalid' class when there are validation errors present ...

Having trouble navigating typescript's "import" syntax in conjunction with compiler options like module and choosing between esnext and commonjs?

I am facing an issue with my typescript project that includes mocha tests. Here is a snippet of how the tests start: import { assert } from "chai"; import "@material/mwc-ripple"; //I need to test a script that uses this describe("simple test", () => { ...

Having trouble showing table data in Angular

My goal is to showcase data from a table created using Spring-Boot Below is my model.ts: export class Quiz1 { QuestionId?: any; Question?: string; OptionsA?: string; OptionsB?: string; OptionsC?: string; OptionsD?: string;} He ...

Performing a conditional query on a Many-to-Many relationship in TypeORM

Operating under a many-to-many relationship between Category and Product entities In need of filtering products based on category ID Attempted to implement the following code after referring to some examples, but encountered difficulties in making it fun ...

How can I use JavaScript to sort through an array and organize the data into groups?

Below is an array that I currently have: Status=["active","inactive","pending","active","completed","cancelled","active","completed"] I am looking to achieve the following result: StatusInfo=["active":3,"inactive":2,"pending":1, "completed":2, "cancelle ...

Transforming a function into an array in TypeScript

I attempted to use the map() function on a dataURL array obtained from the usePersonList() hook, but I am struggling to convert my function to an array in order to avoid errors when clicking a button. import Axios from "axios"; import React, { us ...

Using Bootstrap 4 with Angular 2: A Beginner's Guide

Currently, I am in the process of developing an Angular 2 application using TypeScript. My goal is to integrate the Bootstrap 4 framework with some custom theming. Is this achievable? I have encountered issues with the "ng2-bootstrap" npm package, as it d ...

Incorporating numerous query parameters in Angular version 14

I am currently working on developing a multi-item filter feature for my application and I am faced with the challenge of sending multiple query parameters in the API request to retrieve filtered items. My main concern is whether there is a more efficient ...

There was an issue: Control with the name 'name' could not be located

Whenever I submit the form and try to go back, an error pops up saying "ERROR Error: Cannot find control with the name: 'name'". I'm not sure what I might be missing. Do I need to include additional checks? Below is my HTML file: <div ...

Flattening an array of Map in Typescript involves combining all the

I am working with an array containing entries of type Map<string, number> Is there a way to flatten this array into a single map? Map<string, number>[] converted to Map<string, number> Appreciate any help on this matter. ...

Harnessing the Power of FormControlName and Labels in Angular 6

In my project using Angular 6 and reactive forms, I have a grid with a Detail button that opens a modal window displaying student information. However, when implementing the HTML for the dialog box as shown below, I encountered an error message stating: No ...

Is it possible to utilize the $ symbol within the ngOnInit or constructor functions?

I recently encountered an issue while trying to use the dollar sign ($) in my constructor function, specifically within ngOnInit() and translate.instant. Here is a snippet of the code that caused the problem: declare var $: any; { var SelectedDevice = ...

The transition to CDK version 2 resulted in a failure of our test cases

After upgrading my CDK infrastructure code from version 1 to version 2, I encountered some failed test cases. The conversion itself was successful without any issues. The only changes made were updating the references from version 1 to version 2, nothing ...

What are the steps to resolve TypeScript errors in OpenLayers version 6.6.1?

Since updating to OpenLayers 6.6.1, I have been bombarded with numerous typescript errors related to generics. For example... import olLayerVector from 'ol/layer/Vector'; import olFeature from 'ol/Feature'; public static highlightOver ...

What is the C sharp version of this code structure?

I'm curious to know what the C# syntax is for declaring a property like this: filters: { [arg: string]: string }; ...

Problem with file organizer in Angular application - unable to see files and directories

I successfully integrated a file manager component following this tutorial. Despite no errors in vscode or chrome debug tool, my folders are not visible. Can anyone help me troubleshoot this issue? https://i.stack.imgur.com/ihEak.png I am unsure how to r ...

Exploring the best way to access ViewContainerRef: ViewChild vs Directive

While researching, I came across a recommendation in the Angular Docs that suggests using a directive to access the ViewContainerRef for creating dynamic components. Here is an example of such a directive: import { Directive, ViewContainerRef } from &apos ...

Implement a class attribute to the parent <div> element using React and TypeScript

I'm trying to figure out how to achieve this task. I need to assign a class upon clicking on an element that is not directly in my code, but rather in one of its parent elements. My initial thought was to accomplish this with jQuery using the followi ...

What steps can I take to persistently subscribe to SignalR from an Angular service even in the event of connection failures?

Is there a way to safely attempt to connect to SignalR with intervals between attempts until the connection is established? Also, does anyone have advice on how to handle the different stages of connectivity to the web sockets effectively? We are utilizin ...