Im trying to create a recursive function type that takes the outer most keys in an object type as an argument and returns a new function that then takes the next outer most keys of the object from the given key recursively until no more keys are available.
If the key doesn't extend /${string}
the argument must be of an object type containing the key ans and property:
type Argument<T> = T extends `/${string}` ? T : { [key in T]: string | number }
There will only be one key that wont extend /${string}
per. level.
Example
const values = {
'/aaa': {
'/lorem': {
foo: 'hello world',
'/boo': 12345,
},
},
bbb: {
'/ipsum': {
'/dolor': 'lorem ipsum',
amet: 567890,
},
},
'/ccc': ...
};
foo<typeof values>('/aaa')('/lorem')({ foo: '...' }); // should return type of string
foo<typeof values>('/aaa')('/lorem')('/boo'); // should return type of number
foo<typeof values>('/aaa')('/ipsum'); // should fail
foo<typeof values>({ bbb: '...' })('/ipsum')({ amet: '...' }); // should return type of number
foo<typeof values>({ bbb: '...' })('/ipsum')('/dolor'); // should return type of string
foo<typeof values>({ bbb: '...' })('/lorem'); // should fail
My current code
I have a type that almost does the job., but doesn't work with the non /${string}
extensions :(
type Foo<T extends object> = <K extends keyof T>(args: K) => T[K] extends object ? Foo<T[K]> : T[K];
const values = {
'/aaa': {
'/lorem': {
foo: 'hello world',
'/boo': 12345,
},
},
bbb: {
'/ipsum': {
'/dolor': 'lorem ipsum',
amet: 567890,
},
},
};
const foo = {} as Foo<typeof values>
foo('/aaa')('/lorem')('/boo') // works :D
foo('/aaa')('/lorem')({ foo: '...' }) // fails :(
I tried to handle the args type within the function - But it can't return the next keys :(
type Foo<T extends object> = <K extends keyof T>(
args: K extends `/${string}` ? K : { [key in K]: string | number }
) => T[K] extends object ? Foo<T[K]> : T[K];
const values = {
'/aaa': {
'/lorem': {
foo: 'hello world',
'/boo': 12345,
},
},
bbb: {
'/ipsum': {
'/dolor': 'lorem ipsum',
amet: 567890,
},
},
};
const foo = {} as Foo<typeof values>
foo('/aaa')('/lorem')('/boo') // works :D
const a = foo({ bbb: '...' })('/ipsum') // fails :(
I don't even know if want I'm trying is possible - but if you have any suggestions you would save my day :)
Thanks for your time.
CodePudding user response:
Hmm, problem seems to be with {[key in K]: string | number}
, apparently typescript does not know if T[K]
is an object after that.
If you change it to {[key: string]: K}
it works (but {[key: string|number]: K}
does not). This seems like an issue with Typescript (I am using version 4.7.4.)
Best I can do is using a second parameter instead of an object:
type Foo<T extends object> = <K extends keyof T>(
arg: K,
val?: K extends `/${string}` ? undefined : string|number
) => T[K] extends object ? Foo<T[K]> : T[K];
const foo = {} as Foo<typeof values>
const boo = foo('/aaa')('/lorem')('/boo') // TS: boo is number
const ipsum = foo('bbb', 12)('/ipsum') // TS: ipsum is Foo<{'/dolor': string, amet: number}>