Ways to determine if the type is an enum in TypeScript

Consider the following string enum type:

export enum UserRole {
admin = "admin",
active = "active",
blocked = "blocked"
}

I'm looking to determine whether a specific string emulates this enum type. How can I achieve this?

  const decodedJson = JSON.parse(decodedString)
  if(decodedJson && (decodedJson.role instanceof UserRole) // My attempt at checking if the role is an enum string. However, this method is incorrect.

What would be the proper approach in this case?

Answer №1

I typically avoid using enums in TypeScript because they are one of the features that do not align with TypeScript's language design goals. They exist outside of JavaScript but compile to JavaScript, making it challenging to refer to a JavaScript spec for runtime behavior.


However, if you're interested in understanding the runtime behavior of enums rather than their role in the type system, then we can explore that aspect further. At runtime, an enum simply functions as an object with keys and values. Specifically, when utilizing a string enum, the keys and values will match the defined key-value pairs.

(In the case of a numeric enum, there are also reverse mappings where the value acts as a key and vice versa. While this might be confusing, it doesn't directly relate to your inquiry so I'll refrain from delving into it unless requested.)

The similarity between the keys and values in your enum introduces ambiguity that should be avoided. To address this, let's redefine UserRole as follows:

enum UserRole {
    ADMIN = "admin",
    ACTIVE = "active",
    BLOCKED = "blocked",
}

This enables us to distinguish between whether a given string is a key or value within the enum. For instance, to determine if a provided string role is a key in the enum, we can employ the following approach:

const roleIsEnumKey = role in UserRole;
console.log("role " + role + (roleIsEnumKey ? " IS " : " IS NOT ") + "a key in UserRole");

Similarly, to ascertain if the string corresponds to a value in the enum, you can use:

const roleIsEnumValue = (Object.values(UserRole) as string[]).includes(role);
console.log("role " + role + (roleIsEnumValue ? " IS " : " IS NOT ") + "a value in UserRole");

Assuming your JavaScript version supports Object.values() and Array.prototype.includes(), you can verify the keys and values. Otherwise, feel free to iterate over Object.keys() or any preferred method.

To test the functionality:

check(JSON.stringify({ role: "ADMIN" }));
// role ADMIN IS a key in UserRole 
// role ADMIN IS NOT a value in UserRole

check(JSON.stringify({ role: "admin" }));
// role admin IS NOT a key in UserRole 
// role admin IS a value in UserRole 

check(JSON.stringify({ role: "random" }));
// role random IS NOT a key in UserRole
// role random IS NOT a value in UserRole 

Seems effective. Ultimately, at runtime, the enum behaves like an object, allowing inspection of its keys and values similar to any other object.


Playground link

Answer №2

JS does not natively support Enums, so when using TypeScript, Enum definitions are converted to regular JS objects during compilation. This means that there is no specific Enum data structure in JavaScript for you to directly access or inspect.

For example, the following Enum definition:

enum UserRole {
    admin = "admin",
    active = "active",
    blocked = "blocked"
}

Gets translated to something similar to this:

var UserRole;
(function (UserRole) {
    UserRole["admin"] = "admin";
    UserRole["active"] = "active";
    UserRole["blocked"] = "blocked";
})(UserRole || (UserRole = {}));

Answer №3

According to @NearHuscarl, it is impossible to verify that. Nevertheless, you have the option to confirm whether djson.role (string) corresponds to any of the UserRole (enum) values:

!!(Object.values(UserRole).find(enumValue => enumValue === djson.role)));

Answer №4

When dealing with an enum, it's important to remember that the value can be either a string or number. Therefore, testing against both types is essential.

To address this, we can establish a User-Defined Type Guard as follows:

function isInstance<T extends object>(value: string, type: T): type is T {
    return Object.values(type).includes(value)
}

This function will evaluate to true or false based on whether the value exists in the enum. However, it may not work best for enums where numbers and strings overlap.

enum Animal {
    Cat = 'cat',
    Dog = 'dog'
}

enum Plant {
    Tree = 'tree',
    Flower = 'flower'
}

function isInstance<T extends object>(value: string | number, type: T): type is T {
    return Object.values(type).includes(value)
}

console.log(isInstance('dog', Animal)) // True
console.log(isInstance('dog', Plant))  // False

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

Using Karma-Jasmine to Import Spy without anyImplicitAny

If I include the configuration setting noImplicitAny in the tsconfig.json file of my Angular 4+ project: "noImplicitAny": true, ...and then try to import and use Spy in a unit test: import { Spy } from "karma-jasmine"; I encounter this console error wh ...

Adjust the height of a div vertically in Angular 2+

Recently, I started using angular2 and I've been attempting to create a vertically resizable div without success. I have experimented with a directive for this purpose. Below is the code for my directive: import { Directive, HostListener, ElementRef ...

The element does not recognize the property 'width' since it is not defined in the type of 'GlobalEventHandlers'

I'm trying to determine the size of an image using JavaScript, but I encountered a TypeScript error: const img = new Image(); img.onload = function() { alert(this.width + 'x' + this.height); } img.src = 'http://www.google.com/intl/en_ ...

Typescript's Intersection Types: The Key to Overlapping Properties

Looking to create a type-safe utility function in Typescript 4.0 for comparing properties of two objects, my initial code snippet is below: export function propertiesMatch<O extends object, T extends O, S extends O>(first: T, second: S, props: (keyof ...

Retrieving the Final Value from an Observable in Angular 8

Is there a way to retrieve the most recent value from an Observable in Angular 8? let test = new BehaviorSubject<any>(''); test.next(this.AddressObservable); let lastValue = test.subscribe(data=>console.log(data.value)); Despite my ef ...

Enhancing Angular2 authentication with Auth0 for enabling Cross-Origin Resource Sharing

I have been working on implementing user authentication through Auth0. I followed the instructions provided on their website, but I am encountering authentication issues. Whenever I try to authenticate, an error message appears in the console stating that ...

Modifying data within nested objects using Typescript

Imagine having an object like this: { b954WYBCC4YbsMM36trawb00xZ32: { activity1: "pending", activity2: "pending" }, ​ pby0CAqQ1hTlagIqBTQf6l2Ti9L2: { activity1: "pending", activity2: "pending" } } with the in ...

Converting <reference path/> directive to ESM import: A step-by-step guide

As I embark on developing a TypeScript application, I've reached the realization that I am not a fan of using the <reference path /> triple-slash directive. Instead, I prefer utilizing import 'something'. However, every time I attempt ...

Exciting Angular feature: Dynamic Titles

I am working with an <i> tag <i class="actionicon icon-star" [ngClass]="{'yellow' : data.isLiked}" (click)="Like(data)" aria-hidden="true" title="Liked"></i> In my current set ...

Having trouble with React npm start: 'No chokidar version found' error occurring

After cloning my React-Typescript app on Github Pages and attempting to make some changes, I encountered an issue. Despite running npm install to install all dependencies, when I tried to run npm start, I received the following error message: https://i.st ...

The functionality of routerLink is not functioning as expected when used on button elements

My dashboard is designed to display information retrieved from Firebase. However, I am facing an issue with a button labeled as (more) that should redirect me to a specific page when clicked. Unfortunately, the button doesn't seem to be working as int ...

The routerlink feature consistently directs back to the default page

I am facing an issue where my routerlink does not redirect me to the correct path in app.routes.ts when clicked. Even though the routerlinks are set as 'user/teams' and 'user/dashboard' respectively. I can access the pages by directly ...

Is it possible to extract specific columns from the Convex database?

I am looking to retrieve all columns from a table using the following code snippet. Is there a more efficient way to achieve this? I couldn't find any information in the documentation. Does anyone have a workaround or solution? const documents = await ...

Utilizing images in a compiler run with the option of `allowJS:true` is key

Looking to develop a React Native library using JavaScript instead of typescript, but want to leverage TSC for its ability to generate type declarations from jsdoc comments. However, encountering an issue where local images are not included when the ts com ...

Setting up Typescript for a Node.js project configuration

I am facing an issue with my simple class class Blob { } After compiling it with TypeScript, I encountered the following error message: ../../../usr/lib/node_modules/typescript/lib/lib.dom.d.ts:2537:11 2537 interface Blob { ~~~~ ...

New to React and struggling with updating the DOM

Upon receiving a React project, I am faced with the task of performing a complex state update on my DOM. Here is how my component looks: /** @jsx jsx */ import { jsx } from '@emotion/core'; import { Typography } from '@material-ui/core&ap ...

Managing singleton instances in TypeScript and NextJs across multiple files

Lately, I've been immersed in a personal project that involves creating API endpoints using NextJs and TypeScript to interact with the Discord API via discord.js. Don't worry if you're unfamiliar with the discord API, as the focus here is no ...

Is it possible to define TypeScript interfaces in a separate file and utilize them without the need for importing?

Currently, I find myself either declaring interfaces directly where I use them or importing them like import {ISomeInterface} from './somePlace'. Is there a way to centralize interface declarations in files like something.interface.ts and use the ...

Declaring a subclass type in Typescript: A step-by-step guide

Would it be feasible to create something like this? export abstract class FilterBoxElement { abstract getEntities: any; } export interface FilterBoxControlSuggestions extends FilterBoxElement { getEntities: // some implementation with different pa ...

Strange activities observed during the management of state in react hooks, where the splice() function ends up eliminating the

My current setup involves maintaining a state to handle the addition of new JSX elements: const [display, setDisplay] = useState<IDisplay>({ BookingFormDropDown: [], } ); I have a function in onClick() which adds an elem ...