Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the keys of an object literal as a Typescript type?

Tags:

typescript

I have an object which contains some predefined data for my application, which is stored in a const variable like this:

const data:{[key:string]:any} =Object.freeze({
    some: 123,
    long: {"a":"b"},
    list: ["c"],
    of: "",
    arbitrary: null,
    things: 1.2,
});

The keys of this object are known to the rest of the application. Consider this function which accesses the data object:

function doWork(k) {
    if(!data.hasOwnProperty(k)) throw Error();
    let value = data[k];
    //...
}

This is called with strings like

doWork("things");

I would like to replace that runtime error for invalid keys with a Typescript compile-time check. I would like to be able to write

function doWork(k: keyof data) {
    let value = data[k];
    //...
}

But apparently the keyof operator does not work that way. I get an error TS2304: Cannot find name 'data'.


My workaround: I can extract the keys of the object with something like this:

console.log("\""+Object.keys(data).join("\"|\"")+"\"");

Which I can then copy/paste and define as a type.

type data_key = "some"|"long"|"list"|"of"|"arbitrary"|"things"
export function doWork(k:data_key) {
    let value = data[k];
    //...
}

This feels like a silly hack and is quite inconvenient whenever I have to make a change, because I have to remember to put in this statement at the right place, run the program, and manually copy in the values back into the source code (or realistically, just type in the changes myself).


I am open to a better solution. Is there a language feature that provides the functionality I am looking for?

like image 966
Minette Napkatti Avatar asked May 26 '19 09:05

Minette Napkatti


People also ask

What is 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 define object of objects type in TypeScript?

In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.

Is object a data type in TypeScript?

object is a type that represents the non-primitive type, i.e. anything that is not number , string , boolean , bigint , symbol , null , or undefined . Argument of type 'undefined' is not assignable to parameter of type 'object | null'.Argument of type 'undefined' is not assignable to parameter of type 'object | null'.


1 Answers

Let TypeScript infer the type of data, then extract the keys from the type it infers by using type data_key = keyof typeof data;:

const data = Object.freeze({
    some: 123,
    long: {"a":"b"},
    list: ["c"],
    of: "",
    arbitrary: null,
    things: 1.2,
});

type data_key = keyof typeof data;

function doWork(k: data_key) {
    let value = data[k];
    //...
}

On the playground.

How that works:

  1. TypeScript has advanced type inference and so is able to infer the type of the object initializer passed into Object.freeze with the keys some, long, list, etc. Object.freeze is defined as freeze<T>(o: T): Readonly<T>¹ so it returns a Readonly version of that same inferred type.
  2. keyof gets the keys of a type.
  3. typeof in this context is TypeScript's typeof, not JavaScript's. In TypeScript, there are places it expects runtime value, and other places it expects compile-time types. keyof's operand is a place where a compile-time type is expected, so typeof data returns the compile-time type of data, which is the Readonly version of the inferred type from freeze (with the keys some, long, list, etc.). (If you had x = typeof data, that would be typeof in a context where a runtime value is expected, so it would be JavaScript's typeof, and x would get the runtime value "object".)

¹ There are actually three definitions of freeze in lib.es5.d.ts (one for arrays, one for functions, and one for all other kinds of objects; this is that last one).

like image 84
T.J. Crowder Avatar answered Oct 07 '22 20:10

T.J. Crowder