What measures can be taken to avoid duplicating a string literal that serves as both a data type and a dynamic variable?

The EventBridgeEvent interface in the "@types/aws-lambda" package is defined as:

export interface EventBridgeEvent<TDetailType extends string, TDetail>

It's worth noting that TDetailType extends the string type. An interesting aspect is the ability to define a variable like this:

event: EventBridgeEvent<'stringLiteralFoo', Bar>

I attempted to define the string literal as a variable to avoid copy/pasting, but then I couldn't use it as a type.

const stringLiteralFooVar = 'stringLiteralFoo'
event: EventBridgeEvent<stringLiteralFooVar, Bar>

This resulted in the error:

'stringLiteralFoo' refers to a value, but is being used as a type here. Did you mean 'typeof stringLiteralFoo'?

The complete definition of the Event Bridge Event is as follows:

export interface EventBridgeEvent<TDetailType extends string, TDetail> {
  id: string;
  version: string;
  account: string;
  time: string;
  region: string;
  resources: string[];
  source: string;
  'detail-type': TDetailType;
  detail: TDetail;
  'replay-name'?: string;
}

To determine the detail-type of an event, I'm using the following approach:

event: EventBridgeEvent<'stringLiteralFoo', Bar>
if (event['detail-type'] == 'stringLiteralFoo') {
    // logic here
}

However, my goal is to avoid directly copying and pasting the 'stringLiteralFoo' literal.

Here is a simple example to reproduce the issue:

export interface EventBridgeEvent<TDetailType extends string, TDetail> {
  'detail-type': TDetailType;
  detail: TDetail;
}

const event: EventBridgeEvent<'Foo1' | 'Foo2', Bar> = {
  'detail-type': 'Foo1',
  detail: {}, // of type Bar
}
if (event['detail-type'] == 'Foo1') {
  // logic here
}

interface Bar {}

Therefore, my final question remains: How can I avoid copying and pasting the literal string 'stringLiteralFoo' in the above example?

Answer №1

To prevent the repetition of quoted strings as both literal values and types, you can follow this approach. First, establish a const-asserted array containing the required string literals:

const detailTypes = ["Foo1", "Foo2"] as const;

The const assertion specifies to the compiler that it should not infer the type of detailTypes as mutable and unordered, but rather as an ordered, fixed-length, readonly tuple type consisting of specific string literal types. Consequently, the compiler deduces the following for detailTypes:

// const detailTypes: readonly ["Foo1", "Foo2"]

With this setup, you can leverage various value and type operators to work with the desired types without redundantly specifying string values and types. For instance:

const event: EventBridgeEvent<typeof detailTypes[number], Bar> = {
    'detail-type': detailTypes[0],
    detail: {}, // of type Bar
}

if (event['detail-type'] == detailTypes[0]) {
}

The typeof detailTypes yields readonly ["Foo1", "Foo2"]. To obtain the union type "Foo1" | "Foo2" from this, you can index into it using the number index: typeof detailTypes[number]. This effectively determines the possible return types when indexing detailTypes by valid numerical keys. The result is either "Foo1" or "Foo2," forming the union type.

You can fetch individual element values using detailTypes[0] or detailTypes[1]. If you require their respective types, you can apply the typeof operator directly like so:

type ElemOne = typeof detailTypes[1];
// type ElemOne = "Foo2"

Alternatively, indirectly through:

const elemOne = detailTypes[1];
type ElemOneAlso = typeof elemOne;
// type ElemOneAlso = "Foo2"

These strategies allow you to effectively handle the specified types and values.


Note that in type ElemOne = typeof detailTypes[1], the 1 represents a numeric literal type, while [1] denotes an indexed access type. On the other hand, in const elemOne = detailTypes[1], the 1 signifies a numerical value, and [1] refers to an actual index operation in JavaScript. Despite their similar appearance and functionality, these operations operate at different levels - type vs. value - within the TypeScript-JavaScript hierarchy. The emitted JavaScript code does not retain any type information. Refer to another question answer addressing the segregation of types and values across distinct layers for further insights on this matter.

Visit the code 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

Is there a way to retrieve a specific type from a union-based type by using a generic function in Typescript?

When working with my API, I have noticed a consistent pattern where it returns either a specific type or a TypeScript type called BadResult, as shown below: type Result1 = CreatedPersonResult | BadResult; type Result2 = CreatedRoleResult | BadResult; To s ...

What is the proper way to categorize a "type as a subclass of a class"?

class SuperClass { ... } class SubClass1 extends SuperClass { ... } class SubClass2 extends SuperClass { ... } class SubClass3 extends SuperClass { ... } const foo: ??? = ... Is there a way to assign a type to foo indicating that it is an instance of an ...

Is it possible for me to create a data type representing "potentially undefined strings"?

Just a heads up: I have enabled --strictNullChecks Here is a function I have: export function ensure<T, F extends T>(maybe: T | undefined, fallback: F): T { if (isDefined<T>(maybe)) { return maybe } if (fallback === undefined) { ...

Angular: Utilizing the new HttpClient to map HTTP responses

Within my application, I have a standard API service that communicates with the backend using requests structured like this: post<T>(url: string, jsonObject: object): Observable<T> { return this.http.post<T>(url, JSON.stringify(json ...

Utilizing Lodash in TypeScript to merge various arrays into one cohesive array while executing computations on each individual element

I am working with a Record<string, number[][]> and attempting to perform calculations on these values. Here is an example input: const input1 = { key1: [ [2002, 10], [2003, 50], ], }; const input2 = { key ...

A guide to transforming an object into a JSON query using Elasticsearch

Currently, I am developing a Search Application using Angular7 and elasticsearchJS. Within my data Service, the elasticsearch JSON query body is generated from user inputs. While it functions properly with a simple string query in this.query, there seems t ...

What is the best way to access a property within a typescript object?

I'm working with the following code snippet: handleSubmit() { let search = new ProductSearch(); search = this.profileForm.value; console.log(search); console.log(search.code); } When I run the console.log(search) line, it outputs: ...

What are the steps to imitate a controller?

How can I properly mock this code? I'm in need of some assistance. I'd like to create a test case for this controller. However, as a newcomer, I am struggling with where to begin and how to write the test case. import { Route } from '../co ...

Executing the setDeleted loop causes changes to the entities which are then reflected in the saveChanges

My goal is to delete a hierarchy of objects: Customer->Orders->OrderItems->OrderItemOptions I attempted to set up a nested loop to perform the operations in the correct order - deleting child records before deleting parent records as required by ...

Leveraging JSON.stringify alongside getter/setter in TypeScript

In my TypeScript code, I am utilizing getter/setter accessors. To differentiate between variables and methods with the same name, I have adopted the convention of prefixing the variable with a lower dash, as shown in many examples: private _major: number; ...

What is the method to retrieve Response Headers in cases of an empty response?

Currently, I am working with Angular2 and dotcms. My goal is to retrieve response headers after making a call to subscribe. const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) .append('Access ...

Is it possible to use arguments.callee.name in Typescript?

By setting "strict": false in the tsconfig.json file, we are able to access arguments.callee.name. Is there another configuration option that can be enabled while still maintaining "strict": true in order to achieve the same result? I prefer to adhere to ...

ERROR Error: Uncaught (in promise): ContradictionError: The variable this.products is being incorrectly identified as non-iterable, although it

Seeking a way to extract unique values from a JSON array. The data is fetched through the fetch API, which can be iterated through easily. [please note that the product variable contains sample JSON data, I actually populate it by calling GetAllProducts( ...

Exploring the Synergy of Nestjs Dependency Injection with Domain-Driven Design and Clean Architecture

I am currently exploring Nestjs and experimenting with implementing a clean-architecture structure. I would appreciate validation of my approach as I am unsure if it is the most effective way to do so. Please note that the example provided is somewhat pseu ...

Omit functions from category

This question reminds me of another question I came across, but it's not quite the same and I'm still struggling to figure it out. Basically, I need to duplicate a data structure but remove all the methods from it. interface XYZ { x: number; ...

Retrieve form input from a different component's form using React Hook Form

Currently, I am facing an issue with two React components that contain identical form fields. The challenge is to synchronize a checkbox in one component with the input from the form in the other component when it is checked. This functionality is similar ...

dependency tree resolution failed - ERESOLVE

I've been attempting to run an older project on my new system, but when running npm install, I keep encountering the following issue: https://i.sstatic.net/3AgSX.png Despite trying to use the same versions of Node and NPM as my previous system, noth ...

Dragula drag and drop in a single direction with Angular 2 copy functionality

Attempting to utilize ng2 dragula for one-way drag and drop with copy functionality. Below is the template I am working with: `<div> <div class='wrapper'> <div class='container' id='no-drop' [dragula]=& ...

Using Redux and Typescript to manage user authentication states can help streamline the process of checking whether a user is logged in or out without the need for repetitive checks in the mapStateToProps function

In the process of developing a web application utilizing React & Redux, I am faced with defining two primary "states" - Logged In and Logged Out. To tackle this challenge, I have structured my approach incorporating a union type State = LoggedIn | LoggedO ...

Compilation of various Typescript files into a single, encapsulated JavaScript bundle

After researching countless similar inquiries on this topic, I have come to the realization that most of the answers available are outdated or rely on discontinued NPM packages. Additionally, many solutions are based on packages with unresolved bug reports ...