How can TypeScript associate enums with union types and determine the type of the returned object property?

I have a unique enum in conjunction with its corresponding union type.

type User = { name: string, age: number }

export enum StorageTypeNames {
    User = "user",
    Users = "referenceInfo",
    IsVisibleSearchPanel = "searchPanel",
    PropertyPanelInfo = 'propertyPanelInfo'
};

type StorageType =
    | { name: StorageTypeNames.User, data: User }
    | { name: StorageTypeNames.Users, data?: User[] }
    | { name: StorageTypeNames.IsVisibleSearchPanel, data?: boolean }
    | { name: StorageTypeNames.PropertyPanelInfo, data: {visibility: boolean};

Within a class utilizing these types, I am endeavoring to implement a generic function.

export class StorageHelper {
    private static readonly _storage = window.localStorage;

    static get<T>(type: StorageType): T;
    static get<T>(type: StorageTypeNames): Pick<StorageType, 'data'>;
    static get<T>(type: StorageType | StorageTypeNames): T {
        let data: string | null = null;
        if(typeof type === 'string')
            data = this._storage.getItem(type);
        else
            data = this._storage.getItem(type.name);
        return data ? JSON.parse(data) : null;
    }
}

The objective is to achieve the following outcome: depending on the input parameter type, TypeScript should suggest the properties of the output object.

const userData = StorageHelper.get(StorageTypeNames.User).data. //TypeScript assistance is not available

https://i.sstatic.net/y57rt.png

Could you provide guidance on how to accomplish this? Thank you in advance

Answer №1

When you look at the call signatures of the get() function, it may not immediately convey what is happening. It seems like you are aiming to accept a parameter that is either a subtype of StorageType, and in that case return a value of the same subtype; or a subtype of StorageTypeNames, in which case you should return a value based on the corresponding member of StorageType.

For the first scenario, the get() function needs to be generic with the type T of the type parameter, where T is constrained to be StorageType. The return type should also be T:

static get<T extends StorageType>(type: T): T;

Your error was specifying the type of the type parameter as StorageType instead of using the generic type T constrained to StorageType, causing confusion for the compiler.

On the other hand, for the second scenario, you would want the get() function to be generic with the type K of the type parameter, constrained to be StorageTypeNames. You then need to filter the union of StorageType to retrieve the member whose name property matches with type K. This can be achieved using the utility type Extract<T, U>. Here is how the call signature looks:

static get<K extends StorageTypeNames>(type: K): Extract<StorageType, { name: K }>;

You are free to implement the method according to your requirements:

static get(type: StorageType | StorageTypeNames) {
  /* implementation */
}

If there is a possibility of returning null from the method, ensure you adjust the output type to include null to adhere to strict null checks if enabled by using types like T | null or

Extract<StorageType, {name: K}> | null
.

To test this functionality, examples have been provided and everything appears to work correctly as intended.

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

Implementing a dependent <select> within an HTML table is a useful feature to enhance user experience and organization of

I have set up a table with editable columns where values can be selected from a drop-down menu. I achieved this by using HTML select. The options in the 'Category tier 2' column are based on the selection made in the 'Category tier 1' c ...

Incorporating TypeScript seamlessly into your current Create React App project without the need to modify any existing code or files

While I have already installed Typescript in my project, I am more concerned about adding new .tsx files and ensuring they are type-checked. Simply renaming existing .js files to .tsx is not a viable solution, as it requires refactoring all the existing ...

Developing a Gulp buildchain: Transforming Typescript with Babelify, Browserify, and Uglify

I've configured a buildchain in Gulp, but when I execute the gulp command, it only utilizes one of the two entry points I specify. My goal is to merge the methods outlined in these two resources: and here: https://gist.github.com/frasaleksander/4f7b ...

How can you expand the class of a library object in Animate CC using Createjs?

I am currently in the process of migrating a large flash application to canvas using Typescript, and I'm facing challenges when it comes to utilizing classes to extend library objects. When working with a class library for buttons, class BtnClass { ...

Why did my compilation process fail to include the style files despite compiling all other files successfully?

As English is not my first language, I kindly ask for your understanding with any typing mistakes. I have created a workspace with the image depicted here; Afterwards, I executed "tsc -p ." to compile my files; You can view the generated files here Unf ...

Passing a boolean value from the parent Stepper component to a child step in Angular

I am facing an issue with passing the boolean value isNewProposal from a parent Angular Material stepper component to its children steps. Despite using @Input(), the boolean remains undefined in the child component, even after being assigned a value (true ...

Traversing Abstract Syntax Trees Recursively using TypeScript

Currently in the process of developing a parser that generates an AST and then traversing it through different passes. The simplified AST structure is as follows: type LiteralExpr = { readonly kind: 'literal', readonly value: number, }; type ...

Guide on linking a trust policy to an IAM role through CDK

{ "Version": "2008-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "AWS": [ ...

The utilization of 'fs' in the getInitialProps function is not permitted

Running into an issue while trying to access the contents of a parsed file within getInitialProps when my view loads. The error message "Module not found: Can't resolve 'fs'" is being displayed, and this has left me puzzled - especially cons ...

What is the best way to incorporate my own custom component into the Mui Theme options in order to efficiently modify it?

I've been grappling with this issue for a while now and I just can't seem to get past these type errors. It feels like there's a crucial piece of the puzzle that I'm missing. My goal is to develop my own custom component and then have ...

Issue "Value of type '{}' cannot be assigned to parameter of type 'T | (() => T)'" encountered within a React component containing a type parameter

Currently, I am attempting to achieve the following: function SomeComponent<T>({ children }: PropsType) { const [configuration, setConfiguration] = useState<T>({}) } However, I am encountering this issue: The argument of type '{}&apos ...

Is there a way to open an image.png file in typescript using the "rb" mode?

Is there a way to open png files in typescript similar to the python method with open(path+im,"rb") as f:? I have a folder with png files and I need to read and convert them to base 64. Can anyone assist me with the equivalent method in typescript? This i ...

The KeyConditionExpression is invalid due to the use of multiple attribute names within a single condition

I am trying to query a DynamoDB table using GraphQL TableName: "JobInfo", IndexName: "tableauGSI", KeyConditionExpression: "tableauGSI_Tableau = tableau AND #D BETWEEN :startDate AND :endDate", ExpressionAttributeNames: { "#D": "date" }, ...

Preventing Redundancy in Angular 2: Tips for Avoiding Duplicate Methods

Is there a way I can streamline my if/else statement to avoid code repetition in my header component? Take a look at the example below: export class HeaderMainComponent { logoAlt = 'We Craft beautiful websites'; // Logo alt and title texts @Vie ...

What is the process for obtaining an AccessToken from LinkedIn's API for Access Token retrieval?

We have successfully implemented the LinkedIn login API to generate authorization code and obtain access tokens through the browser. Click here for image description However, we are looking to transition this functionality to an ajax or HTTP call. While w ...

The initial processing step for the export namespace is to utilize the `@babel/plugin-proposal-export-namespace-from` plugin. Previous attempts to resolve this issue have been

I encountered a problem with my application and found two related questions on the same topic. However, due to a lack of reputation, I am unable to comment or ask questions there. That's why I'm reaching out here... Recently, I integrated Redux ...

My goal is to develop a secure login system with authentication on the Angular platform

login(data: any) { this.user.getUsers().subscribe( (users) => { const user = users.find((u) => u.username === data.username && u.userpassword === data.password); if (user) { // Valid username and password, ...

Can the automatic casting feature of TypeScript be turned off when dealing with fields that have identical names?

Imagine you have a class defined as follows: Class Flower { public readonly color: string; public readonly type: string; constructor(color: string, type: string) { this.color = color; this.type = type; } Now, let's introduce anoth ...

Creating a function in Typescript to extend a generic builder type with new methods

Looking to address the warnings associated with buildChainableHTML. Check out this TS Playground Link Is there a way to both: a) Address the language server's concerns without resorting to workarounds (such as !,as, ?)? b) Dodge using type HTMLChain ...

What is the best method for releasing an NX library along with all its bundled dependencies?

This problem is quite common in GitHub's NX repository, but I have not been able to find a solution there. Within my workspace, I have two buildable libraries: ui/avatar and ui/icon, as well as a publishable library named bar The goal is to utilize ...