Typescript : can it validate that a key exists in an object ?

The problem is simpler than you think it is when you only look at the title.
If an object contains a complex data structure like this :

const structure = { a : { b : {c : { d : 1 } } } }

With a function that can read inside the object :

function read(path: string) {
return path.split('.').reduce((acc, value) => acc[value], structure);
}

console.log(read('a.b.c.d')); // 1


How can you verify with Typescript that the path exists in structure ?

There is a quite simple answer when the object is flat :
function read(path: keyof typeof structure)

… But the object is not flat.
Then you have to implement a descending recursive type to define the type.
Rather than sticking to the example, we can define a utility type so that any similar problem can be resolved with a generic solution.

type DeepKeyOf<T, U> = {
  [P in keyof T]?: T[P] extends U ? [P, DeepKeyOf<T[P], U>] | [P] : [P];
}[keyof T];

Where T is the type of the structure, and U is all the types that can be considered primitive.
For the above example, the code would become

function read(path: DeepKeyOf<keyof typeof structure, number>) {
let flattenedPath: string[] = [];
while (foundPath && foundPath.length) {
flattenedPath.push(foundPath[0]);
foundPath = foundPath[1] as any;
}
}

console.log(read(['a', ['b', ['c', ['d']]]])); // 1

Even though it does exactly the same when compiled in javascript, the path is now checked by typescript.

console.log(read(['a', ['b', ['c', ['d']]]])); // ok
console.log(read(['a', ['b']])); // ok
console.log(read(['a', ['b', ['e', ['d']]]])); // typescript compilation error

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.