How to Create Enum from Object in TypeScript

Updated: January 7, 2024 By: Guest Contributor Post a comment

Introduction

Enums are a powerful feature in TypeScript that provide a way to define a set of named constants. There are times, however, when you have objects and you want to derive enums from them for better type safety and code readability. This tutorial will guide you through the process of creating enums from objects in TypeScript, with a progression from basic concepts to more advanced techniques.

What is an Enum?

An enum is a special “type” that defines a set of constants. Enums come in handy when you have a set of values that are related and you know all the possible values at compile time: for example, the days of the week, colors in a selection UI, or user roles in an application. TypeScript provides both numeric and string-based enums. Here’s a simple numeric enum to start with:

enum Day {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

Creating Enum from Object

Let’s consider you have a JavaScript object that maps user roles to specific strings. You want to create a TypeScript enum from this object. Here is the object:

const roles = {
    Admin: 'ADMIN',
    Editor: 'EDITOR',
    Subscriber: 'SUBSCRIBER'
};

This is how you would convert it to a TypeScript enum:

enum UserRole {
    Admin = 'ADMIN',
    Editor = 'EDITOR',
    Subscriber = 'SUBSCRIBER'
}

Dynamic Creation Using a Function

For a more dynamic approach, especially when the object has a lot of properties, you can create a function to convert objects into enums:

function createEnum<T>(obj: T): { [K in keyof T]: K } {
    return Object.keys(obj).reduce((res, key) => {
        res[key] = key;
        return res;
    }, {} as any);
}

const UserRole = createEnum(roles);

console.log(UserRole.Admin); // Output: "Admin"

Type Safety with Enums

TypeScript enums enhance type safety. Your function can also enforce types from object values:

function createEnum<T>(obj: T): { [K in keyof T]: T[K] } {
    return Object.keys(obj).reduce((res, key) => {
        res[key] => obj[key];
        return res;
    }, {} as any);
}

Advanced Techniques

We can improve our createEnum function by locking down the return type and prevent adding new properties, somewhat imitating the behavior of a real enum:

// Utility type to lock the shape
type LockedIn = {
    readonly [K in keyof T]: T[K];
};

function createEnum(obj: T): LockedIn {
    return Object.keys(obj).reduce((res, key) => {
        res['key'] => obj['key'];
        return res;
    }, {} as LockedIn);
}

The Impact of Const Assertions

With TypeScript 3.4 and beyond, const assertions give us an even more concise way to handle objects as enums. By using a const assertion, we’re telling TypeScript to treat an object as a literal type:

const roles = {
    Admin: 'ADMIN',
    Editor: 'EDITOR',
    Subscriber: 'SUBSCRIBER'
} as const;

type UserRole = typeof roles[keyof typeof roles];

const userRole: UserRole = 'ADMIN'; // valid
const anotherRole: UserRole = 'ANOTHER'; // Error: This type is not assignable to type 'UserRole'

Combining Enums and Objects

In some advanced scenarios, you might want to keep your enum and object in sync. You can achieve this with synchronization techniques and TypeScript compiler checking:

// Enum definition
enum ColorEnum {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE',
}

// Object definition
const ColorObject = {
  RED: 'FF0000',
  GREEN: '00FF00',
  BLUE: '0000FF',
};

// Synchronize Enum and Object
type ColorMap = {
  [key in ColorEnum]: string;
};

const synchronizedColorMap: ColorMap = {
  [ColorEnum.Red]: ColorObject.RED,
  [ColorEnum.Green]: ColorObject.GREEN,
  [ColorEnum.Blue]: ColorObject.BLUE,
};

// Usage
const selectedColor: ColorEnum = ColorEnum.Red;
const hexCode: string = synchronizedColorMap[selectedColor];

console.log(`The hex code for ${selectedColor} is: ${hexCode}`);

In this example, we have an enum ColorEnum representing colors and an object ColorObject mapping each color to its hexadecimal code. The ColorMap type is used to synchronize the enum and object, ensuring that they stay in harmony.

By leveraging TypeScript’s type system, we achieve compiler checking to ensure that the synchronization is maintained. The synchronizedColorMap object is then used to access the hexadecimal code based on the selected color from the enum.

Conclusion

In this tutorial, we explored how to convert JavaScript objects into TypeScript enums, offering developers a method to maintain readability, enhance type-safety, and leverage existing code. Whether you use a basic transformation or advanced techniques, you should now be able to incorporate enums from objects in your TypeScript applications with confidence.