TypeScript Object with Dynamic Keys

When working with TypeScript, there might be scenarios where you do not have predetermined keys for an object. These dynamic keys can come from user input, external data, or other runtime sources. In this guide, we will explore different ways to define, create, and work with objects having dynamic keys in TypeScript.

Basic Definition

For objects with dynamic keys, TypeScript provides the Record type and index signatures.

Index Signature

You can use an index signature to specify that an object could have any number of properties of a certain type.

interface StringDictionary { [key: string]: string; } const obj: StringDictionary = { key1: "value1", key2: "value2", };

In this example, the keys are strings, and the values are also strings. You can change the value type as per your requirements.

Using the Record type

The Record type allows you to create an object type where you specify the key type and value type.

const obj: Record<string, string> = { key1: "value1", key2: "value2", };

Using Enums as Keys

If you have a limited set of dynamic keys, enums can be a handy choice.

enum Keys { FIRST = "firstKey", SECOND = "secondKey", } type EnumDictionary = Record<Keys, string>; const obj: EnumDictionary = { [Keys.FIRST]: "value1", [Keys.SECOND]: "value2", };

Combining Static and Dynamic Keys

In real-world scenarios, you might have objects with both static and dynamic keys.

interface MixedDictionary { staticKey: string; [key: string]: string | number; // Can accommodate the staticKey too } const obj: MixedDictionary = { staticKey: "staticValue", dynamicKey1: "value1", dynamicKey2: "value2", };

Nested Dynamic Keys

Objects can have nested structures with dynamic keys at various levels.

interface NestedDictionary { [key: string]: { innerKey: string; [nestedKey: string]: string | number; }; } const obj: NestedDictionary = { outerKey1: { innerKey: "value", nestedKey1: "nestedValue1", }, outerKey2: { innerKey: "value", nestedKey2: 42, }, };

Constrained Dynamic Keys

At times, you'd want to constrain the dynamic keys to a specific set of values, perhaps based on another array or tuple.

const validKeys = ['a', 'b', 'c'] as const; type ValidKeysType = typeof validKeys[number]; type ConstrainedDictionary = Record<ValidKeysType, string>; const obj: ConstrainedDictionary = { a: "valueA", b: "valueB", c: "valueC", };

Here, obj can only have keys 'a', 'b', or 'c'.

Handling Unknown Keys

Sometimes, you might be unsure about the object's shape. TypeScript provides an unknown type for such cases.

type UnknownKeyDictionary = { [key: string]: unknown; }; const obj: UnknownKeyDictionary = { key1: "value1", key2: 42, key3: { inner: "value" }, };

Using unknown forces you to type-check values before operating on them, enhancing type safety.

Using with Functions

When dealing with functions that operate on objects with dynamic keys, you can utilize generics for better type inference.

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const obj = { key1: "value1", key2: 42, }; const result1 = getValue(obj, 'key1'); // result1 inferred as string const result2 = getValue(obj, 'key2'); // result2 inferred as number

Remember, while dynamic keys provide flexibility, they can also introduce challenges in ensuring type safety. Always strive for the right balance between flexibility and safety when modeling your types.

Invite only

We're building the next generation of data visualization.