Can TypeScript be configured to recognize the generic object assignment illustrated below?

Take a look at this code snippet:

type Type = 'one' | 'two' | 'three'

type TypeSelection = 'one' | 'two'

interface InnerObject<T extends Type> {
  name: string,
  type: T
}

type Obj<K extends Type> = {
  [P in K]?: InnerObject<P>
} & {
  [P in TypeSelection]: InnerObject<P>
}


const obj: Obj<Type> = {
  one: {
    name: 'a',
    type: 'one'
  },
  two: {
    name: 'a',
    type: 'two'
  }
}

function getInnerObject<T extends Type>(key: T) {
  const selectedObj: InnerObject<T> | undefined = obj[key]
  const defaultObj = obj['one']
}

Check out the Playground for this code.

Even though obj[key] should return InnerObject<T>, there is an error thrown by TypeScript for this assignment:

const selectedObj: InnerObject<T> | undefined = obj[key]
.

The error message reads:

Type 'InnerObject<"one"> | InnerObject<"two"> | InnerObject<"three"> | undefined' is not assignable to type 'InnerObject<T> | undefined'.
  Type 'InnerObject<"one">' is not assignable to type 'InnerObject<T>'.
    Type '"one"' is not assignable to type 'T'.
      '"one"' can be assigned to the constraint of type 'T', but 'T' may have a different subtype of constraint 'Type'.(2322)

It appears that it is trying to assign all possibilities from the right side to the left side.

Is there a way to resolve this issue without using type assertion?

Answer №1

Extensive documentation on the specific issue you're encountering is not readily available (the closest reference found is ms/TS#56905). In a broad context, TypeScript struggles with executing various type operations on generic types efficiently. It often delays evaluation (resulting in valid assignments being rejected) or oversimplifies the generic type parameter to its constraint (leading to unsafe scenarios).

In principle, it should hold that (X & Y)[K] can be assigned to X[K] (provided that K is a recognized key of

X</code). When dealing with specific <code>X
, Y, and K types, TypeScript can validate this by fully evaluating the intersection and the indexed access. However, if K happens to be a generic type, TypeScript fails to verify it properly and defers the assessment, treating it as an obscure entity:

const selectedObject = obj[key];
//    ^? const selectedObject: Obj<Type>[T]

The connection between each key T and the associated type InnerObject<T> isn't directly encoded in the type Obj<Type>; rather, it's indirectly represented through the intersection. Consequently, TypeScript overlooks this link:

const selectedObject: InnerObject<T> | undefined = obj[key]; // error!

This error signifies what would occur if you tried assigning obj[key] to InnerObject<K> for any unrelated generic K. The only safe assignment scenario is when obj[key] matches InnerObject<K> for every possible K, which gives rise to "cross-assignment" complications.


To ensure successful assignment, there is a need to assist TypeScript in perceiving the operation more straightforwardly. If you possess a mapped type structured as {[P in K]: F<P>} and utilize the key K to index into it, TypeScript will recognize it as assignable to F<K>. This aligns with the concept of a distributive object type elaborated in microsoft/TypeScript#47109. Given that the mandatory mapped type is encompassed within the Obj<K> definition, widening any Obj<K> to that segment should be secure.

The suggested course of action is as follows:

function getInnerObject<T extends Type>(key: T) {
    const o: { [P in Type]?: InnerObject<P> } = obj; // widen
    const selectedObj: InnerObject<T> | undefined = o[key] // index into widened thing
    const defaultObj = obj['one']
}

Initially broaden obj from Obj<Type> to o having the type

{[P in Type]?: InnerObject<P>}
. Such expansion is permissible because it aligns with one element of the intersection perfectly, exploiting TypeScript's leniency toward widening from X & Y to X. Then proceed to access this mapped type using the generic key represented by T, thereby enabling TypeScript to approve the resultant value as assignably compatible with InnerObject<T> | undefined.

Note that retaining the obj value for future use is crucial. The variable o served solely to conduct widening securely. Achieving this without an additional variable is feasible using the safe widening notion x satisfies T as T, where satisfies verifies the type and as widens it:

type OT = { [P in Type]?: InnerObject<P> };
const selectedObj: InnerObject<T> | undefined = (obj satisfies OT as OT)[key];

Playground link to 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

Ensure Rxjs waits for the completion of the previous interval request before moving forward

Scenario: It is required to make an API call every 3 minutes to update the status of a specific service within the application. Here is my existing code snippet: interval(180000) .subscribe(() => this.doRequest ...

Managing Geolocation in Ionic2 presenting challenges

Attempting to utilize Geolocation in ionic2 for device location access. Referred to the official documentation on https://ionicframework.com/docs/native/geolocation/. Successfully installed the necessary packages: $ ionic plugin add cordova-plugin-geoloca ...

Can template literal types be utilized to verify if one numeric value is greater than another?

I am attempting to define the Record for migration functions, which use the direction of the migration as the key: v${number}-v${number}, Considering that these migrations are all UP, they need to be validated as v${first-number}-v${first-number + 1} and ...

Attempting to perform an API invocation on a distant endpoint utilizing NestJS

var unirest = require("unirest"); var req = unirest("GET", "https://edamam-edamam-nutrition-analysis.p.rapidapi.com/api/nutrition-data"); req.query({ "ingr": "1 large apple" }); req.headers({ &qu ...

Guide on efficiently inserting values into an array of objects

I'm looking to extract specific values from the enum below enum p { XDR = 1, PT1M = 2, PT1M_ = 2, PT5M = 3, PT5M_ = 3, PT15M = 4, PT15M_ = 4, PT1H = 5, PT1H_ = 5, P1D = 6, P1D_ = 6, P7D = 7, P1W = 7, ...

Tips for successfully passing store state as a prop in React-Redux with TypeScript

Having trouble passing information from the initial state of the store to a component where it's supposed to be rendered. Despite a console.log in the component showing that it's undefined, there doesn't seem to be any issue with the initial ...

The command "ng test" threw an error due to an unexpected token 'const' being encountered

Any assistance with this matter would be greatly appreciated. I am in the process of constructing an Angular 5 project using angular/cli. The majority of the project has been built without any issues regarding the build or serve commands. However, when a ...

Enhancing React with TypeScript and Redux

I am trying to implement React-Redux with TypeScript to dispatch an action. When using mapDispatchToProps(), I am unsure how to define the type of dispatch. Here is my code: This is the component file: import * as React from 'react'; i ...

Array that accepts the type of the first element as its generic parameter

There was a type challenge The task was to create a generic function called First that takes an array T and returns the type of its first element. type arr1 = ["a", "b", "c"]; type arr2 = [3, 2, 1]; type head1 = First<arr1>; // expected: 'a& ...

Converting image bytes to base64 in React Native: A step-by-step guide

When requesting the product image from the backend, I want to show it to the user. The issue is: the API response contains a PNG image if the product has an image, but returns a (204 NO Content) if the product does not have an image. So, I need to display ...

I'm seeing an issue where my SafeResourceUrl is being displayed as undefined within a function of the identical class

export class ClassName implements OnInit { url: string = "{{'content.url' | translate}}"; urlSafe: SafeResourceUrl; constructor(public sanitizer: DomSanitizer, private translate: TranslateService) { } ngOnInit() { ...

Ensure that Vuex state can only be accessed through a getter function

Our goal is to have a variable in our Vuex store that can only be accessed through the getter function. To do this, we must ensure that the variable is protected from external access outside of the store. What options are available to us? ...

TypeScript: a sequence of symbols representing a particular <type>

Perhaps I'm going crazy. I have a roster of potential nucleotides and a corresponding type: const DNA = ['G', 'C', 'T', 'A'] as const; type DNA = typeof DNA[number]; So, a DNA strand could be a sequence of an ...

Disregard the possibility of React Hook "React.useEffect" running multiple times during execution

I am currently working on a React component that accepts an array of pairs within the props. Each pair consists of: index 0: a function that will be triggered every time the dependencies change, index 1: an array of dependencies. Using the Component & ...

Determine whether something has the potential to be a string in TypeScript

I am looking to create a TypeScript type that can identify whether an element has the potential to be a string. This means the element should have the type "string" or "any", but not "number", "boolean", "number[]", "Person", etc. I have experimented wit ...

Omit certain table columns when exporting to Excel using JavaScript

I am looking to export my HTML table data into Excel sheets. After conducting a thorough research, I was able to find a solution that works for me. However, I'm facing an issue with the presence of image fields in my table data which I want to exclude ...

Is it possible to use a type assertion on the left hand side of an assignment in TypeScript?

While reading the TypeScript documentation, I came across type assertions but it seems they are limited to expressions only. I am looking to assert the type of a variable on the left side of an assignment statement. My specific scenario involves an Expres ...

Running a TypeScript file on Heroku Scheduler - A step-by-step guide

I have set up a TypeScript server on Heroku and am attempting to schedule a recurring job to run hourly. The application itself functions smoothly and serves all the necessary data, but I encounter failures when trying to execute a job using "Heroku Schedu ...

Incorporating types and optional arguments in Typescript programming

Develop a program using JavaScript to display all the properties of a given object Example object: var student = { name : "David Rayy", sclass : "VI", rollno : 12 }; Your task is to enhance this program by incorporating typing, optional arguments, an ...

Display the dynamic change of minutes and seconds every second on an Ionic HTML page

I created a JavaScript countdown counter that displays minutes and seconds using the following line of code: document.getElementById("demo").innerHTML = Number(this.minutes) + 'm' + Number(this.seconds) + 's '; However, on iOS devices ...