Issue
We have an API that can return a lot of data and therefore has a fields
param to control which props are returned. Assuming this API definition:
interface Foo {
A?: string;
B?: number;
C?: boolean;
D?: string[];
E?: number[];
}
We get code all over that does stuff like this:
// stripped-down definition local to a project
interface MyFoo {
A?: string;
C?: boolean;
}
const fields = ['A', 'C'];
...
const myFoo = await axios.get(... some url passing "fields" ...) as MyFoo;
I'd like to simplify all of that (and reduce risk of errors) by just basing all of that off the main API definition. That means using the same array of keys of Foo to construct a new type as well as using that array in the API call itself.
What I have so far:
declare function getFoo<T extends Foo, K extends keyof T>(fields: K[]): Pick<T, K>;
This gets me MOST of the way there:
const myFoo = getFoo(['A', 'C']);
type MyFoo = typeof myFoo; // type MyFoo = { A?: string; C?: boolean }
But is there any way to avoid the awkward two-step process here?
I tried something like ReturnType<typeof getFoo>
but that gets the whole original definition since there's no way that I can see to pass the fields array into that.
Solution
Ok, I think I get what the issue here is. You are trying to use the type of myFields
to create another type with Pick
. The problem here is the type FooFieldsArray
. By giving myFields
the type FooFieldsArray
any information about specific elements in the array is already lost.
You could instead use a generic function to initialise FooFieldsArray
.
function createFields<T extends (keyof Foo)[]>(fields: [...T]){
return fields
}
const myFields = createFields(["A", "C"]);
// ^? const myFields: ["A", "C"]
Now this next line works too:
type foo = GetMyFoo<typeof myFields>
// ^? type foo2a = { A?: string | undefined; C?: boolean | undefined; }
If you want to specify a seperate generic type for createFields
, there is a problem. You will still need to infer the type of the passed tuple but since TypeScript does not have partial type inference, currying is the only option without code repetition here.
function createFields<T>() {
return <F extends (keyof T)[]>(fields: [...F]) => fields
}
type GetObj<T, K extends (keyof T)[]> = Pick<T, K[number]>;
const fields = createFields<Foo>()(['A']);
type obj = GetObj<Foo, typeof fields>;
Answered By - Tobias S.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.