Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Enforce a type to be "string literal" and not <string>

Problem

Is there a way in Typescript to define a type that is only a string literal, excluding string itself?

Note that I am not talking about a certain list of string literal; for which, a simple union of "Value1" | "Value2", or an enum type would work. I am talking about any string literal, but not string itself.

Example Code

type OnlyStringLiterals = ...; // <--- what should we put here?

const v1: OnlyStringLiterals = "hi"; // should work
const v2: OnlyStringLiterals = "bye"; // should work
// and so should be for any single string value assigned

// But:
const v3: OnlyStringLiterals = ("red" as string); // should NOT work -- it's string

Use Case

I am doing Branding on the types in my code, and I am passing a brand name, as a template, to my parent class. See the code below:

abstract class MyAbstractClass<
    BRAND_T extends string,
    VALUE_T = string
> {
    constructor(private readonly _value: VALUE_T) { }

    getValue(): VALUE_T { return this._value; }

    private _Brand?: BRAND_T; // required to error on the last line, as intended!
}

class FirstName extends MyAbstractClass<"FirstName"> {
}

class AdminRole extends MyAbstractClass<"AdminRole"> {
}

class SubClassWithMissedName extends MyAbstractClass<string> {
   // I want this to error! ........................ ^^^^^^
}

function printName(name: FirstName) {
    console.log(name.getValue()); 
}

const userFirstName = new FirstName("Alex");
const userRole = new AdminRole("Moderator");

printName(userRole); // Already errors, as expected

Playground Link

I want to make sure every subclass is passing exactly a string literal, and not just string to the parent class.

like image 263
Aidin Avatar asked Feb 12 '20 09:02

Aidin


People also ask

Does TypeScript enforce types?

Use the Record utility type to enforce the type of an object's values in TypeScript, e.g. type Animal = Record<string, string> . The Record utility type constructs an object type, whose keys and values are of specific type. Copied!

What is string literal type in TypeScript?

The string literal type allows you to specify a set of possible string values for a variable, only those string values can be assigned to a variable. TypeScript throws a compile-time error if one tries to assign a value to the variable that isn't defined by the string literal type.

How do you use string literals in TypeScript?

Hence, you can treat a variable that has a string literal type like a variable of type string . You can access properties, call methods, and use operators, just as you would with regular strings: const eventName: "click" | "mouseover" = "click"; eventName. length; // 5 eventName.

Can a string be a literal?

A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string.


1 Answers

I found an answer that works for my use case, but is not the most reusable one. Just sharing it anyway.

Thought Process

I believe it's not possible to have one solid type to represent what I wanted, because I cannot even think what will show up in VS Code if I hover over it!

However, to my knowledge, there is a function-style checking in Typescript for types that you can pass a type in and expect a type back, and finally assign a value to it to see if it goes through.

Type-checking using a Generic Type and a follow-up assignment

Using this technique I am thinking about the following template type:

type TrueStringLiterals<T extends string> = string extends T ? never : true;

const v1 = "hi";
const check1: TrueStringLiterals<typeof v1> = true; // No error :-)

const v2 = "bye";
const check2: TrueStringLiterals<typeof v2> = true; // No error :-)

const v3 = ("red" as string);
const check3: TrueStringLiterals<typeof v3> = true; // Errors, as expected!

Playground Link

Easier in an already-passed Generic Type

Also, in my use case, I am doing:

abstract class MyAbstractClass<
    BRAND_T extends (string extends BRAND_T ? never : string),
    VALUE_T = string
> {
...

Playground Link

... which works like a charm!

like image 50
Aidin Avatar answered Oct 03 '22 23:10

Aidin