Tips for guaranteeing a Typescript string enum with identical key and value pairs

I am looking for a way to create a generic type that verifies certain criteria on an enum:

  1. all fields must be strings
  2. all values must be equal to their respective keys

For example, the following enums would meet the requirements:

enum correct1 {
  bar = 'bar',
  baz = 'baz',
}

enum correct2 {
  quux = 'quux',
}

However, the following enums would not:

enum wrongFoo {
  bar = 'bar',
  baz = 'WRONG',
}

enum wrongFoo2 {
  bar = 1
}

Can you provide the correct syntax to achieve this validation?

Answer №1

To implement a manual compile-time check, where you need to add something manually after defining your enum, you can use the following code:

type EnsureCorrectEnum<T extends { [K in Exclude<keyof T, number>]: K }> = true;

Then, you can have the compiler check

EnsureCorrectEnum<typeof YourEnumObjectHere>
. If it compiles without errors, everything is good; otherwise, there is an issue:

type Correct1Okay = EnsureCorrectEnum<typeof correct1>; // okay
type Correct2Okay = EnsureCorrectEnum<typeof correct2>; // okay

type WrongFooBad = EnsureCorrectEnum<typeof wrongFoo>; // error!
//   ┌─────────────────────────────> ~~~~~~~~~~~~~~~
// Types of property 'baz' are incompatible.

type WrongFoo2Bad = EnsureCorrectEnum<typeof wrongFoo2>; // error!
//   ┌──────────────────────────────> ~~~~~~~~~~~~~~~~
// Types of property 'bar' are incompatible.

The error messages are quite descriptive in this scenario.

Hopefully, this explanation is helpful to you and good luck with your implementation!

Link to code

Answer №2

Here is an alternative approach that eliminates the need to define a new type for each check:

const validateKeyValues = <T>(kv: { [K in keyof T]: K }) => {};

enum success {
  apple = 'apple',
  banana = 'banana',
}
enum failure {
  apple = 'apple',
  banana = 'bad',
}

validateKeyValues(success);
validateKeyValues(failure); // Error: Type 'failure.banana' is not assignable to type '"banana"'.

Answer №3

It seems like a utility we developed might be useful in your case. While it doesn't exactly generate an enum, it does produce a type-safe object where the keys mirror their names and implement keyof typeof for added string-type security.

This tool was invented prior to the existence of string enums, hence the label "enum" although it functions more like an object. Nonetheless, it serves as a great alternative to hardcoded strings.

/**
 * This function creates a pseudo string enum object. Usage example:
 *     const Ab = strEnum(['a', 'b']);
 *     type AbKeys = keyof typeof Ab;
 * @param keys keys in the enum
 * @returns enum object
 */
export function generateStringEnum<T extends string>(keys: T[]): {[K in T]: K} {
    return keys.reduce((res, key) => {
        res[key] = key;
        return res;
    }, Object.create(null));
}

const Ab = generateStringEnum(['a', 'b']);
type AbKeys = keyof typeof Ab;

const Bc = generateStringEnum(['b', 'c']);
type BcKeys = keyof typeof Ab;

console.log(Bc.blah) // Compilation error - blah property does not exist
// Error: invalid string
const b: AbKeys = "e"; 
// Although an enum would trigger an error, this isn't truly an enum
// Primary drawback of this approach
const a: AbKeys = Bc.b;

Even if it may not directly meet your requirements, this tool could prove beneficial for individuals who aren't mandated to utilize enums.

Answer №4

Enums in Typescript are treated as objects, allowing you to utilize the Object.keys function to retrieve all keys within the enum and then compare them to their corresponding values. It's important to note that since the keys returned by Object.keys are strings, the values within the enum must also be strings.

enum correct1 {
    bar = 'bar',
    baz = 'baz',
}

enum correct2 {
    quux = 'quux',
}

enum wrongFoo {
    bar = 'bar',
    baz = 'WRONG',
}

enum wrongFoo2 {
    bar = 1
}

function isEnumValid<T extends {}>(validatedEnum: T) : boolean {
    return Object.keys(validatedEnum).every(k => k === validatedEnum[k]);
}

console.log(isEnumValid(correct1)); // true
console.log(isEnumValid(correct2)); // true
console.log(isEnumValid(wrongFoo)); // false
console.log(isEnumValid(wrongFoo2)); // false

Answer №5

After a bit of searching online, I came across an alternative method that might be helpful

Another Source

export enum OPTIONS {
  OPTION1 = <any>"option1",
  OPTION2 = <any>"option2"
}

This approach seems to be effective

  function(option: OPTIONS) {

  ...

  option: OPTIONS.OPTION2,

  ... (or)

  if (option === OPTIONS.OPTION2) ...

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

Exploring the power of Prosemirror with NextJS through Tiptap v2

Greetings everyone, I am a newcomer to Stack Overflow and I am reaching out for assistance regarding an issue that has arisen. The problem at hand pertains to the development of the Minimum Viable Product (MVP) for my startup which specializes in creating ...

Building a dynamic and fast Vite project using "lit-ts" to create a visually appealing static website

I recently put together a project using Vite Lit Element Typescript and everything seemed to be running smoothly on the development server. However, when I tried running npm run build, only the compiled JS file was outputted to the /dist folder without any ...

Encountered an unexpected token error when executing karma-coverage in a project using TypeScript

I have been working on a simple Angular/Typescript project that includes 12 basic unit tests which all pass successfully. However, I am now looking to measure the code coverage of these tests. Despite trying different methods, I have not been able to achie ...

"Error 404: The file you are looking for cannot be found on [custom company domain]. Please check

My attempts to retrieve a Google Drive file using its file ID with a service account in NodeJS have been unsuccessful. The requests are failing with an error indicating a lack of access: code: 404, errors: [ { message: 'File not found: X ...

Show a Toast in React without relying on useEffect to manage the state

I have successfully implemented the Toast functionality from react-bootstrap into my application using the provided code. However, I am unsure if it is necessary to utilize useEffect to set show with setShow(items.length > 0);. Would it be simpler to ...

Exploring the concept of inheritance and nested views within AngularJS

I've encountered a challenge while setting up nested views in AngularJS. Utilizing the ui-router library has been beneficial, but I'm facing issues with separate controllers for each view without proper inheritance between them. This results in h ...

I am having trouble getting the guide for setting up a NextJS app with Typescript to function properly

For some time now, I have been experimenting with integrating Typescript into my NextJS projects. Initially, I believed that getting started with Typescript would be the most challenging part, but it turns out that installing it is proving to be even more ...

Angular is encountering a circular dependency while trying to access a property called 'lineno' that does not actually exist within the module exports

I am working on an Angular project and using the Vex template. My project utilizes Angular 9 and Node.js v15.2.0. Every time I run the project with the command ng serve -o, it displays a warning message. https://i.stack.imgur.com/8O9c1.png What could b ...

What is the method to have VIM recognize backticks as quotes?

Currently working in TypeScript, I am hoping to utilize commands such as ciq for modifying the inner content of a template literal. However, it appears that the q component of the command only recognizes single and double quotation marks as acceptable ch ...

Angular Reactive Forms may not properly update other inputs when binding a control to multiple inputs

While working with reactive forms, I encountered an issue where accessing the same control in multiple inputs seemed to result in one-way data binding (input-to-model). If I make edits in one input, it updates the model correctly but does not refresh the o ...

Why am I unable to apply the keyof operator from one type to another type, even though both types have identical keys defined but different value types?

Consider this code snippet. I am encountering a TypeScript error specifically on the last compat[k] line with the following error message: Type 'keyof T' cannot be used to index type 'Partial<CompatType>' export type KeysOfType ...

Type of tuple without a specific order

Exploring Typescript typings has led me to ponder how to create a type that is a tuple with unordered element types. For example: type SimpleTuple = [number, string]; const tup1: SimpleTuple = [7, `7`]; // Valid const tup2: SimpleTuple = [`7`, 7]; // &ap ...

What are the steps to integrate material-ui with styled-components effectively?

import styled from "styled-components"; import Button from "@material-ui/core/Button"; export const StyledButton = styled(Button)` margin: 20px; `; I'm having trouble adjusting the button styling. How can I add a margin to the ...

Encountering Typescript issues while trying to access @angular/core packages

Recently, I made an update to my Ionic app from Angular 7 to Angular 8, and an odd error popped up: https://i.sstatic.net/icZOb.png The issue lies in the fact that I am unable to access any of the standard classes stored in the @angular/core module. This ...

extract keys and values from an array of objects

I would like assistance with removing any objects where the inspectionScheduleQuestionId is null using JS. How can we achieve this? Thank you. #data const data = [ { "id": 0, "inspectionScheduleQuestionId": 1, ...

Encountering a Next.js event type issue within an arrow function

After creating my handleChange() function to handle events from my input, I encountered an error that I'm unsure how to resolve. Shown below is a screenshot of the issue: https://i.sstatic.net/fWJA2.png I am currently working with Next.js. In React ...

Employing Multer and Express in conjunction with TypeScript

Overview Currently, I am working on a project that involves creating a user-friendly website where individuals can easily upload images. For this particular task, I have employed Node.js, React, Multer, and Typescript. Issue at Hand app.post('/admi ...

Is there a way to use Regex to strip the Authorization header from the logging output

After a recent discovery, I have come to realize that we are inadvertently logging the Authorization headers in our production log drain. Here is an example of what the output looks like: {"response":{"status":"rejected",&quo ...

Struggling to integrate D3.js with React using the useRef hook. Any suggestions on the proper approach?

I'm currently working on creating a line chart using d3.js and integrating it into React as a functional component with hooks. My approach involved utilizing useRef to initialize the elements as null and then setting them in the JSX. However, I encou ...

What is the method for enabling imports from .ts files without file extensions?

While trying to open a Svelte project with TypeScript, I encountered an issue where all imports from .ts files were showing "Cannot resolve symbol" errors. https://i.stack.imgur.com/FCVxX.png The errors disappear when the .ts extension is added to the im ...