Create object property string path generator with typescript
Wow, that was a large noun cluster. Well, what is it anyway?
Now, using some libraries/frameworks you might sometimes need to generate a string that would represent a path to a property of some object, for example when you need to perform a get on Angular's FormGroup
or have nested form field in React Hook Form
.
You could create several monstrous constants, ie something akin to:
const nestedAddressField = 'user.contacts.address';
Or maybe use template strings and enums, but there is a better way.
Typescript 4 bring along amazing stuff: Template Literal Types
. These are the solution.
Now, using this amazing feature we can create a type with infinite recursion and a helper function to generate all possible property paths an object has for us.
Check this out. First let us create a type that would basically be a colletion of all possible string paths in an object:
// For convenience
type Primitive = string | number | bigint | boolean | undefined | symbol;
// To infinity and beyond >:D
export type PropertyStringPath<T, Prefix=''> = {
[K in keyof T]: T[K] extends Primitive | Array<any>
? `${string & Prefix}${ string & K }`
: `${string & Prefix}${ string & K }` | PropertyStringPath <T[K], `${ string & Prefix }${ string & K }.`> ;
}[keyof T];
Basically what it does is iterating over object's properties and returning each field as a string with a given prefix. Initially the prefix is empty string, which is nothing. If it encounters a primitive or an array, it returns a string with prefix, if it encounters an object, it then invokes itself recursively, but adds a dot to the prefix. Easy-peasy.
Now what's left to do is to create a simple factory that can generate us helper functions to provide hints:
export function propertyStringPathFactory<T, R=string>(): (path: PropertyStringPath<T>) => R {
return (path: PropertyStringPath<T>) => (path as unknown as R);
}
So now instead of walking on eggshells with strings we can use helper function that safeguards us from making mistakes.
Have fun ^_^
Oh, and by the way, there's an npm package with this utility 💪
Top comments (9)
The code works indeed but should not be used because of infinite recursion. The Compiler will heavily be used and your IDE will be working way less fast. Just so others know. For having a recursion limiting approach checkout "Paths" type of this stackoverflow answer:
stackoverflow.com/questions/584343...
Unfortunately, in practice you can't get infinite recursion, it appears there is some sort of a hidden safeguard against it.
I actually tried to kill the playground page by deliberately applying type helper to a self referencing interface and trying to get hints, which was supposed to give me infinite amount of them and hang everything, but it just doesn't work that way. I wonder what are the checks under the hood that guard against it.
UPT: oh, yeah, there's a safeguard in ts
if (instantiationDepth === 50 || instantiationCount >= 5000000)
:DOkay. Well I wrote the comment cuz vscode complained about the error and it was way slower than usual with type checking..
Anyways, nice post and answer : )
Thanks @bwca for this
As I copy/paste, I get this error
Type instantiation is excessively deep and possibly infinite.(2589)
That's right, it is infinite in theory, so using it would require suppressing the error message with
ts-ignore
.Can we made it run for such as 10 levels of nesting not more?
Yes, we can, I addressed it in one of the later articles, look for
DEFAULT_DEPTH_LEVEL
on the page, it has a code example :)Hi i've tried to use your code, but there is an error.
dev-to-uploads.s3.amazonaws.com/up...
Might it be that the version of typescript you are trying to use it with is under version 4? Template Literal Types are not available in versions below 4.
You can check it in the playground: once you choose version under 4, you start getting errors