Determining the data type of an object property using a variable

I just joined a pre-existing project, where there are some global variables set by the backend. These globals are defined in an interface:

interface IGlobals {
    feature_foo: boolean
    feature_bar: boolean
    someOtherProp: string
}

const globals: IGlobals = {
    feature_foo: true,
    feature_bar: false,
    someOtherProp: 'hello'
}

My task now is to create a function that checks if a specific feature flag exists within these global variables. I have assumed that any property starting with feature_ will always be a boolean (which is true for our case):

// Use this as: `const isEnabled = useFeature('foo')`
function useFeature(feature: string): boolean {
  const featureKey = `feature_${feature}`

  if (globals.hasOwnProperty(featureKey)) {
    // Assuming globals starting with `feature_` are always booleans
    return globals[featureKey as keyof IGlobals] as boolean
  }

  return false
}

Although it works, I feel like it's somewhat of a workaround.
I find the as boolean statement particularly bothersome. I used it because otherwise TypeScript would rightly raise concerns that the value could be a boolean or string, which doesn't match the function's return type.

Is there a way to improve this?

I suspect that "lookups" may provide a solution, but I don't fully understand that concept.

Answer №1

Absolutely! You have the option to make this code fully type-safe without the use of a .hasOwnProperty lookup-

function useFeature(feature: 'foo' | 'bar'): boolean {
  const featureKey = `feature_${feature}` as const;

  return globals[featureKey];
}

Alternatively, you can achieve the same result with:

function useFeature(feature: 'foo' | 'bar'): boolean {
  return globals[`feature_${feature}`];
}

This method utilizes template literal types as keys, ensuring full type safety when the feature parameter is restricted to exact feature names. However, if you prefer more flexibility in checking the feature argument within an if statement, consider using this helper function for narrowing types-

function isValidFeature(x: string): x is 'foo' | 'bar' {
  return x === 'foo' || x === 'bar';
}

Don't forget to synchronize the feature list here with updates made in IGlobals!

With that in place, you can simply do-

function useFeature(feature: string): boolean {
  return isValidFeature(feature) && globals[`feature_${feature}`];
}

No need for casting, everything is thoroughly checked and type-safe.

Edit:- To enhance efficiency, establish a direct link between isValidFeature and IGlobals so that any updates to features in one location reflect in the other. This leads us into type level programming-

const _featureNames = ['foo', 'bar'] as const;
const featureNames: string[] = [..._featureNames];

type FeatureKeys = {
  [featureName in typeof _featureNames[number]]: `feature_${featureName}`;
}[typeof _featureNames[number]]

type Features = {
  [featureKey in FeatureKeys]: boolean;
}

interface IGlobals extends Features {
    someOtherProp: string;
    andAnotherOne: number;
}

declare const globals: IGlobals;

function isValidFeature(x: string): x is typeof _featureNames[number] {
  return featureNames.includes(x);
}

Now, updating feature names within the _featureNames tuple will automatically update everything else. Convenient!

Try it out on the playground.

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

How can you eliminate the prop that is injected by a Higher Order Component (HOC) from the interface of the component it produces

In my attempt to create a Higher Order Component, I am working on injecting a function from the current context into a prop in the wrapped component while still maintaining the interfaces of Props. Here is how I wrap it: interface Props extends AsyncReque ...

What is the reason for the lack of template reference output from ContentChilden?

I am striving to create a modular reusable table by using directives to generate columns for custom cell content. However, after multiple days of effort, I am unable to access my TemplateRefs and they always turn out empty. The Angular version being used ...

Is there a way to extract data from a single line?

In my code, I have a simple object retrieved like this: getSelectedRecipients(event) { this.aliasesService.getRecipients(event.nr) .subscribe( res => { this.recipients = res; this.isVisible = true; }, err =&g ...

Configuring global runtime variables in NextJS for server-side operations

Currently, I am utilizing the instrumentation.ts file in NextJS to retrieve configuration dynamically when the server starts up. My goal is to have this configuration accessible during runtime for all API routes and server-side components. What would be th ...

Expanding upon TypeScript by inheriting interfaces

My goal is to create a User module with multiple classes such as UserDetail, UserResetPassword, and more. These classes will have some common properties that need to be shared. One approach is to declare the properties in each class and initialize them in ...

Efficient method of triggering an action on a subcomponent in React Redux without the need to pass props down the component tree

Currently in the process of learning how to utilize react, redux, and react-redux with a straightforward requirement. I aim to display something similar to the layout below... -------------------------------- | title 1 |----------| | | descriptio ...

What is the reason for this assignment not being activated?

After going through the official xstate tutorial, I decided to create my own state machine inspired by a post on dev.to by a member of the xstate team. Everything is working fine except for the fact that the output is not being updated. It seems like the ...

Angular mat-table experiencing issues with matToolTip functionality

My Angular project is using Angular Material 16x, but for some reason, the matToolTip is not displaying at all. I have experimented with various versions, including a basic matTooltip="hello world", but I just can't seem to get it to work. I have come ...

TS - Custom API hook for making multiple API requests - incompatible type with 'IUseApiHook'

What is my objective? I aim to develop a versatile function capable of handling any type of API request for a frontend application. Essentially, I want to add some flair. Issue at hand? I find myself overwhelmed and in need of a fresh perspective to revi ...

Redux ConnectedProps will always have a type of never

I am facing an issue while attempting to connect a component to my Redux store following the steps outlined in the official documentation guide. The props that are connected seem to be coming through as type never. Here is a snippet of my code: Type defi ...

Breaking down large reducer into smaller reducers

I am currently working on a feature reducer (slice reducer) called animals. My goal is to separate these reducers into categories such as mammals, birds, fishes, and more. Initially, I thought this would be a smooth process using the ActionReducerMap. How ...

The Angular Universal error arises due to a ReferenceError which indicates that the MouseEvent is not

I am encountering an error while trying to utilize Angular Universal for server-side rendering with the command npm run build:ssr && npm run serve:ssr. This is being done in Angular8. /home/xyz/projects/my-app/dist/server/main.js:139925 Object(tslib__WEB ...

Building a custom CellRenderer in AGGrid using TypeScript within a React environment

Currently, I am utilizing React along with TypeScript and attempting to create a custom CellRenderer in AGGrid. My code is structured like this: PriorityCellRenderer.tsx import React from 'react'; function PriorityCellRenderer(props:any) { co ...

Showcasing a single object in an IONIC/Angular application using the ngIF directive

I am in need of assistance as I have encountered an issue. Essentially, I am fetching an object from an external API (Strapi) using the GET method, but when attempting to display it on Views with ngIF, it fails to show up. https://i.sstatic.net/nFyOE.png ...

Tally up identical words without considering differences in capitalization or extra spaces

Let's take an example with different variations of the word "themselves" like "themselves", "Themselves", or " THEMSelveS " (notice the leading and trailing spaces), all should be considered as one count for themselves: 3 ...

Unable to transfer object from Angular service to controller

I am currently utilizing a service to make a $http.get request for my object and then transfer it to my controller. Although the custom service (getService) successfully retrieves the data object and saves it in the responseObj.Announcement variable when ...

Retrieving JSON Information in HTML using Angular 4

Is it possible to retrieve specific data from a JSON file in my HTML using Angular 4's MatCellDef? Specifically, I am interested in accessing the FROM, TO, PERCENT, and SUBTRACT values of the RateBands Array. JSON Data Sample: [ { "year ...

Using React Router DOM's History Object in Typescript Triggers an Error

I am encountering an issue with a stateless component that receives the History object from react-router-dom and passes it down to a stateful component through props. Typescript is raising an error when trying to pass the history object as a prop. Below a ...

Choosing a value type within an interface or object declaration

Is it possible to extract a nested type object from an interface or parent type? interface IFake { button: { height: { dense: number; standard: number; }; }; otherStuff: string; } type Button = Pick<IFake, 'button'& ...

"Embrace the powerful combination of WinJS, Angular, and TypeScript for

Currently, I am attempting to integrate winjs with Angular and TypeScript. The Angular-Winjs wrapper functions well, except when additional JavaScript is required for the Dom-Elements. In my scenario, I am trying to implement the split-view item. Although ...