I get a json response (which becomes a object later on) from an API call which I can dynamically restrict to certain fields with queryparams.
I have typed the complete response already.
Now what I want to achive is to create a type which takes my given fields and returns an object-type, which only contains the given properties.
I am aware that there are utility types like Pick, but they do not work with nested objects.
example:
interface ResponseInterface {
a: {
a: {
a: number;
b: string;
};
b: {
a: string;
b: boolean;
};
};
b: {
a: object | null;
b: string;
}[];
}
const filters = ['a.a.a', 'a.b.b', 'b.a']; //nested properties seperated by dots
const fakeFullResponse: ResponseInterface = { //calling the api without any filters
a: {
a: {
a: 123,
b: 'aab',
},
b: {
a: 'aba',
b: true,
},
},
b: [
{
a: null,
b: 'array bb',
},
{
a: {},
b: 'array bb',
},
],
};
interface ExpectedType { //after applying those filters to the object
a: {
a: {
a: number;
};
b: {
b: boolean;
};
};
b: {
a: null | object
}[];
}
const fakeFilteredResponse: ExpectedType = { //calling the api with those fields above would get me this
a: {
a: {
a: 123,
},
b: {
b: true,
},
},
b: [
{
a: null,
},
{
a: {},
},
],
};
Is there any way to dynamically create the ExpectedType
interface given the ResponseInterface
Interface and the filters
? If needed the format of the filters
array can be changed, as long as the nesting structure remains intact
First of all, the definition of your ResponseInterface.b
makes little sense compared to your ExpectedType.b
, since the former is a Tuple with fixed length 2, while the latter is an array of arbitrary length (update: OP had fixed this). So to make sense, allow me to assume that you mean the following:
interface ResponseInterface {
a: {
a: {
a: number;
b: string;
};
b: {
a: string;
b: boolean;
};
};
b: {
a: null | object;
b: string;
}[];
}
Next, if the filter is written in the form of strings, then you'll need TypeScript to parse your string literal types, which is a feature only exists in the upcoming version 4.1. Even with that power, it is a lot more difficult to rearrange the type structure due the the fact that you list the filters one by one instead of structurally.
So I will recommend you to format your filter in a type instead:
type filters = { 'a': { 'a': 'a', 'b': 'b' }, 'b': 'a' };
The format should be self-explained. Then you can use the following utility:
type PickStructure<T, F> = F extends keyof T ? { [k in F]: T[k] } : {
[k in keyof F]: k extends keyof T ? (
T[k] extends (infer U)[] ? PickStructure<U, F[k]>[] : PickStructure<T[k], F[k]>
) : never;
};
type ExpectedType = PickStructure<ResponseInterface, filters>;
And it works. See this Playground Link