How to Create Union of Generic Types that Extend String in TypeScript ?
Creating a union of generic types that extend string in TypeScript involves defining a type that accepts only string literals. This allows for the creation of a type that represents a specific set of strings, providing type safety and flexibility in code.
These are the following ways to do that:
Table of Content
Approach 1: Using an Array of Strings
In this approach, we are using an array of strings, and define a generic type `StringUnion<T extends string>` where `T` represents the string literals. This approach creates a union type from the elements of the array.
Example: We Define a generic 'StringUnion` type accepting string literals. Creates an array of specific strings, then logs each element in a function taking the same type as an argument.
type StringUnion<T extends string> = T[];
type MyStringType = "HTML" | "CSS" | "Javascript";
const myStringArray: StringUnion<MyStringType> =
["HTML", "CSS", "Javascript"];
function resultFunction(language: StringUnion<MyStringType>): void {
language.forEach(languageItem => console.log(languageItem));
}
resultFunction(myStringArray);
Output:
[ 'HTML', 'CSS', 'Javascript' ]
[ 'HTML', 'CSS', 'Javascript' ]
[ 'HTML', 'CSS', 'Javascript' ]
Approach 2: Using a Mapped Type
In this approach we are using a mapped type, define `StringUnion` accepting string literals. Map over each literal, assigning it to itself, resulting in a union of string literals. Use this type to create an array, enabling a union of strings.
Example: This example shows that it defines a `StringUnion` type mapping over string literals. Assigns a specific string to a variable. Logs the variable in a function taking the same type as an argument.
type StringUnion<T extends string> = { [K in T]: K }[T];
type MyStringType = "HTML" | "CSS" | "Javascript";
const myString: StringUnion<MyStringType> = "Javascript";
function myResult(language: StringUnion<MyStringType>): void {
console.log(language);
}
myResult(myString);
Output:
JavaScript
Approach 3: Using Conditional Types:
In TypeScript, using conditional types, a generic type `StringUnion<T>` can be defined to ensure that `T` extends `string`, resulting in a union type of string literals. It checks if `T` extends any type, mapping it to `string`, or else `never`, thus forming the union.
Example: In this example we defines a StringUnion type, which resolves to string literals from a given type. It ensures type safety by restricting assignments to only the specified literals.
type StringUnion<T extends string> = T extends any ? T : never;
// Example usage:
type MyStringType = "HTML" | "CSS" | "JavaScript";
// StringUnion<MyStringType> will resolve to "HTML" | "CSS" | "JavaScript"
let myString: StringUnion<MyStringType>;
myString = "HTML"; // Valid
myString = "CSS"; // Valid
myString = "JavaScript"; // Valid
// myString = "TypeScript"; // Error:
//"TypeScript" is not assignable to type "HTML" | "CSS" | "JavaScript"
console.log(myString);
type StringUnion<T extends string> = T extends any ? T : never;
// Example usage:
type MyStringType = "HTML" | "CSS" | "JavaScript";
// StringUnion<MyStringType> will resolve to "HTML" | "CSS" | "JavaScript"
let myString: StringUnion<MyStringType>;
myString = "HTML"; // Valid
myString = "CSS"; // Valid
myString = "JavaScript"; // Valid
// myString = "TypeScript"; // Error:
//"TypeScript" is not assignable to type "HTML" | "CSS" | "JavaScript"
console.log(myString);
Output:
JavaScript
Approach 4: Using Template Literal Types
In this approach, we use template literal types to concatenate string literals into a union type.
Example:
type StringUnion<T extends string> = `${T}`;
// Example usage:
type MyStringType = "HTML" | "CSS" | "JavaScript";
// StringUnion<MyStringType> will resolve to "HTML" | "CSS" | "JavaScript"
let myString: StringUnion<MyStringType>;
myString = "HTML"; // Valid
myString = "CSS"; // Valid
myString = "JavaScript"; // Valid
// myString = "TypeScript"; // Error:
//"TypeScript" is not assignable to type "HTML" | "CSS" | "JavaScript"
console.log(myString);
type StringUnion<T extends string> = `${T}`;
// Example usage:
type MyStringType = "HTML" | "CSS" | "JavaScript";
// StringUnion<MyStringType> will resolve to "HTML" | "CSS" | "JavaScript"
let myString: StringUnion<MyStringType>;
myString = "HTML"; // Valid
myString = "CSS"; // Valid
myString = "JavaScript"; // Valid
// myString = "TypeScript"; // Error:
//"TypeScript" is not assignable to type "HTML" | "CSS" | "JavaScript"
console.log(myString);
Output:
JavaScript
Approach 5: Using Type Aliases with Utility Types
Using type aliases with utility types like Extract allows defining a union type constrained to valid strings. For example, `type MyUnion = Extract<'a' | 'b' | 'c', string>` ensures `MyUnion` only includes `'a'`, `'b'`, or `'c'`, enhancing type correctness and safety.
Example:
type MyUnion = Extract<'a' | 'b' | 'c', string>;
// Usage
const value1: MyUnion = 'a'; // Valid
console.log(value1); // Output: 'a'
const value2: MyUnion = 'b'; // Valid
console.log(value2); // Output: 'b'
// Uncommenting the following line will cause a TypeScript error
// const value3: MyUnion = 'd';
// Error: Type '"d"' is not assignable to type 'MyUnion'.
type MyUnion = Extract<'a' | 'b' | 'c', string>;
// Usage
const value1: MyUnion = 'a'; // Valid
console.log(value1); // Output: 'a'
const value2: MyUnion = 'b'; // Valid
console.log(value2); // Output: 'b'
// Uncommenting the following line will cause a TypeScript error
// const value3: MyUnion = 'd';
// Error: Type '"d"' is not assignable to type 'MyUnion'.
Output:
a
b
Approach 6: Using keyof with Constraints
Using the keyof keyword with constraints allows you to create a union of generic types that extend strings. This approach ensures that only the keys of the specified type are included in the union, providing type safety and flexibility.
Syntax:
type StringUnion<T extends Record<string, any>> = keyof T;
Example: The following example demonstrates how to define a union type using keyof with constraints. This approach creates a union of the keys of the given type T, ensuring that only string keys are included.
type StringUnion<T extends Record<string, any>> = keyof T;
interface Languages {
HTML: string;
CSS: string;
JavaScript: string;
TypeScript?: string;
}
type LanguageKeys = StringUnion<Languages>;
// Nikunj Sonigara, LanguageKeys will resolve to "HTML" | "CSS" | "JavaScript" | "TypeScript"
let myLanguage: LanguageKeys;
myLanguage = "HTML"; // Valid
myLanguage = "CSS"; // Valid
myLanguage = "JavaScript"; // Valid
myLanguage = "TypeScript"; // Valid
console.log(myLanguage);
type StringUnion<T extends Record<string, any>> = keyof T;
interface Languages {
HTML: string;
CSS: string;
JavaScript: string;
TypeScript?: string;
}
type LanguageKeys = StringUnion<Languages>;
// Nikunj Sonigara, LanguageKeys will resolve to "HTML" | "CSS" | "JavaScript" | "TypeScript"
let myLanguage: LanguageKeys;
myLanguage = "HTML"; // Valid
myLanguage = "CSS"; // Valid
myLanguage = "JavaScript"; // Valid
myLanguage = "TypeScript"; // Valid
console.log(myLanguage);
Output
TypeScript