In Typescript, object strings can be enforced to be used from the parent object similar to

I am currently developing an API wrapper for a lower level library that utilizes enums to map human readable keys to internal values. In order to enhance security, I want to only use the enum keys and not the underlying values in any logging or other functions. I am seeking a way to dynamically create an object that mirrors the original enum but with keys/values that are identical. Additionally, I want to ensure that the parent object keys are passed as parameters similar to an enum, rather than allowing direct use of the string value.

enum Colors {
    red = '$r5',
    green = '$g2',
    lightBlue = '$b9',
    darkBlue = '$b1',
}

function getColor(color: Colors) {
    return color;
}


getColor(Colors.darkBlue); // succeeds
getColor('darkBlue'); // fails as expected

function createKeyEnum<E>(e: E): {[k in keyof E]: k extends keyof E ? k : never} {
    return Object.keys(e).reduce((p, v) => {
        (p as any)[v] = v;
        return p;
    }, {} as {[k in keyof E]: k extends keyof E ? k : never});
}

const NewColors = createKeyEnum(Colors);
type NewColors = keyof typeof NewColors;


// forward mapping
function getOldColor(color: NewColors) {
    return Colors[color];
}

// reverse mapping
function getNewColor(color: Colors) {
    const reverse: any = {};
    Object.entries(Colors).forEach(([k, v]) => reverse[v] = k);
    return reverse[color] as NewColors;
}

getOldColor(NewColors.darkBlue); // succeeds
getOldColor('darkBlue'); // succeeds, but should fail like an enum
const color = getNewColor(Colors.darkBlue);
// typeof color == 'red' | 'green' | 'lightBlue' | 'darkBlue'
// should be: typeof color == NewColors

Is it possible to achieve this functionality with advanced typings, or are enums a unique feature that cannot be replicated using other TypeScript typings? In the code snippet provided, my NewColors types only result in a union of literal key strings. While this serves its purpose, it does permit users to utilize the string value directly. How can I associate it with the parent symbol NewColors and ensure that values are strictly derived from it like in an enum?

Answer №1

To ensure the object generated by createKeyEnum() has properties with values that are nominally typed, it is important to distinguish between similar values based on their names or declaration sites. While NewColors.darkBlue may be the same as "darkBlue" during runtime, the compiler should treat them as distinct entities due to their names.

In TypeScript, most of the type system is structural, meaning two types can be considered compatible even if they have different names or declaration sites. There has been a long-standing request for official support for nominal typing in TypeScript, which is yet to be addressed.

While enums in TypeScript offer some semblance of nominal typing, it is not feasible to generate an enum programmatically. This limitation necessitates a different approach.


To achieve nominal typing in TypeScript, a string literal type can be "branded" by intersecting it with a nominal or nominal-ish type. This branding serves to simulate nominal types within the type system, allowing for the desired behavior.

By using an intersection, the branded type can be assigned to the string literal type but not vice versa. This ensures type safety in the code base and prevents unintended errors.

Depending on the intersected type, a type assertion may be required within the implementation of createKeyEnum() to convince the compiler of the expected type. This helps in maintaining the integrity and consistency of the nominal typing.


One possible implementation involves defining a type alias Brand<T, U> that brands T with a type dependent on U. The specifics of how Brand is defined can vary, but the overarching goal remains the same.

With the proposed implementation of Brand, createKeyEnum() can be structured in a way that enforces nominal typing and prevents unintended type violations.

By utilizing a _tag property, nominal typing can be simulated within the TypeScript type system, safeguarding against potential misuse or type inconsistencies. The use of structural typing in this context helps maintain type safety.


In scenarios where a higher level of type security is needed, utilizing a class with a private member can further enhance the nominal typing mechanism. This approach restricts the creation and manipulation of instances, reducing the likelihood of type errors.

By leveraging the private nature of the _tag member and constructor, the BrandClass type can be treated as nominal in nature, enhancing the overall type safety of the code base.

Overall, implementing nominal typing in TypeScript involves a combination of structural and private member approaches to ensure type integrity and prevent unintended type violations.

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

Vue Error: The method "reduce" is not a function

Currently implementing Vue.js with Typescript and aiming to utilize reduce for summing up the values of desktopCnt and mobileCnt from the deviceCount array to display their total numbers. The deviceCount array structure is as follows: [ { " ...

The type 'GetServerSidePropsContext<ParsedUrlQuery, PreviewData>' does not include property X

My current setup includes: type Session = { bearer: string, firstName: string, lastName: string, etc... }; interface ServerContext extends GetServerSidePropsContext { session: Session, }; export type ServerProps<P extends { [key: string]: ...

Issue with Formgroup in Angular Reactive Form - Validation not functioning as expected

I am working with a form group which is defined below: get createItem(): FormGroup { return this.formBuilder.group({ name: ['', Validators.required], email: ['', Validators.required], mobile: ['', V ...

Tips for properly waiting for an AngularFire2 subscription to complete before executing the subsequent lines of code within Angular2

Having an issue with my Angular2 Type Script code. The goal is to access Questions from a Firebase Database by subscribing to a FirebaseListObserver: this.af.list('/questions').subscribe( val => { this.questions = val console.log(th ...

Leveraging async/await in Firebase functions along with the once() method

Recently diving into the world of TypeScript, I've been navigating my way through with relative ease. However, I've encountered a perplexing issue while working with async/await. The problem lies within this code snippet - the 'await' ...

What is the best way to only buffer specific items from an observable source and emit the rest immediately?

In this scenario, I have a stream of numbers being emitted every second. My goal is to group these numbers into arrays for a duration of 4 seconds, except when the number emitted is divisible by 5, in which case I want it to be emitted immediately without ...

Leveraging ngOnChanges to determine the display of an overlay based on input alterations

Working with TS/Angular on a web application, I made the decision to refactor some code that handles displaying different overlays. Instead of having separate code for each overlay, I consolidated them into one "master overlay" and created a function withi ...

Creating Algorithms for Generic Interfaces in TypeScript to Make them Compatible with Derived Generic Classes

Consider the (simplified) code: interface GenericInterface<T> { value: T } function genericIdentity<T>(instance : GenericInterface<T>) : GenericInterface<T> { return instance; } class GenericImplementingClass<T> implemen ...

Identifying whether a particular area is represented in a geographic map array presents a significant challenge

Having an issue with typescript currently. I have a variable that contains different string entries representing x, y positions. The entries are as follows: ["3,3","3,4","3,5","2,3","2,4","2,5","-1,-2","-2,- 2","-2,-1"] My goal is to determine if this land ...

Tips for customizing the legend color in Angular-chart.js

In the angular-chart.js documentation, there is a pie/polar chart example with a colored legend in the very last section. While this seems like the solution I need, I encountered an issue: My frontend code mirrors the code from the documentation: <can ...

Snackbar and RTK Query update trigger the error message: "Warning: Cannot update during an existing state transition."

I've built a basic ToDos application that communicates with a NodeJS backend using RTK Query to fetch data, update state, and store cache. Everything is functioning properly as expected with the communication between the frontend and backend. Recently ...

How can I design a Typescript interface that accommodates both strings and other data types?

I am working on designing an interface that allows for an array of objects and strings to be stored. For instance: const array = [ '', {id: '', labels: ['']} ] I attempted to achieve this using the following code: export ...

Unable to establish a connection between the HTML element and the TypeScript variable

I'm facing an issue with my code where the function that worked perfectly for register and login is not functioning properly on the index page. Even though there seems to be no errors in the login and register functions, I have a form with an input s ...

Variable Scope is not defined in the TypeScript controller class of an AngularJS directive

I have implemented a custom directive to wrap ag grid like so: function MyDirective(): ng.IDirective { var directive = <ng.IDirective>{ restrict: "E", template: '<div style="width: 100%; height: 400px;" ag-grid="vm.agGrid ...

Searching for particular information within an array of objects

Seeking guidance as a newbie on how to extract a specific object from an array. Here is an example of the Array I am dealing with: data { "orderid": 5, "orderdate": "testurl.com", "username": "chris", "email": "", "userinfo": [ ...

What is the best way to retrieve entire (selected) objects from a multiselect feature in Angular?

I'm facing an issue with extracting entire objects from a multiselect dropdown that I have included in my angular template. Although I am able to successfully retrieve IDs, I am struggling to fetch the complete object. Instead, in the console, it dis ...

The ng-model-options in Angular 2 is set to "updateOn: 'change blur'"

Currently working with angular 2, I am seeking a solution to modify the behavior of ngModel upon Enter key press. In angular 1.X, we utilized ng-model-options="{updateOn: 'change blur'}". How can this be achieved in angular 2? For reference, her ...

Passing a type as an argument in Typescript

How can I pass a type as a parameter in Typescript? type myType = {} const passingType = (t: Type) => { const x : t = {} } passingType(myType); I keep receiving TypeScript errors. 't' is referencing a value, but it is being used as a t ...

When a webpage is moved, the globalProperties variable of "vue3 typescript" is initialized to null

main.ts const app = createApp(App) .use(router) .use(createPinia()) .use(vuetify) .use(vue3GoogleLogin, googleLogin) const globalProps = app.config.globalProperties; globalProps.isDebugMode = true; vue-shim declare ...

A guide on activating the <b-overlay> component when a child triggers an Axios request in vue.js

Is there a way to automatically activate the Bootstrap-vue overlay when any child element makes a request, such as using axios? I am looking for a solution that will trigger the overlay without manual intervention. <b-overlay> <child> ...