Given an API that can provide "expanded" versions of an object graph, I'm looking to get a simple way of defining the types returned.
As an example, for the these definitions
interface A {
id: string;
q: string;
b: B;
cs: C[];
}
interface B {
id: string;
prev: B;
c: C;
}
interface C {
id: string;
foo: string;
ds: D[];
}
interface D {
id: string;
e: E;
}
interface E {
id: string;
}
I would need to derive e.g. the following types
type A0 = {
id: string,
q: string,
b: { id: string },
cs: Array<{id: string}>,
};
type A1 = {
id: string,
q: string,
b: {
id: string,
prev: { id: string },
c: { id: string },
},
cs: Array<{
id: string,
foo: string,
ds: Array<{id: string}>,
}>,
};
type A2 = {
id: string,
q: string,
b: {
id: string,
prev: {
id: string,
prev: { id: string },
c: { id: string },
},
c: {
id: string,
foo: string,
ds: Array<{
id: string,
}>,
},
},
cs: Array<{
id: string,
foo: string,
ds: Array<{
id: string,
e: { id: string},
}>,
}>,
};
What I have are the following types (and so on for larger levels)
export type Retain0Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? Array<{ id: string }>
: { id: string }
: never
};
export type Retain1Level<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain0Levels<TP[I]> }
: Retain0Levels<TP>
: never
};
export type Retain2Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain1Level<TP[I]> }
: Retain1Level<TP>
: never
};
type Primitive = string | Function | number | boolean | symbol | undefined | null;
This works fine, but there are of course two problems with this:
- I have to define a type for each level. It would be nice to have just two types (I assume one doesn't work or would get very messy): a leaf (level0) and a recursively defined higher order level.
- the consumer of my API access handler has to cast the return type (e.g.
A
) to the type they requested (by providing a parameter indicating how many levels to expand). It would be nice to have the return type based on the input parameter provided.
As an example for (2), say we have a function that calls backend API, e.g. getAs(levels: number): A[]
. I'd like this to be getAs(levels: number): Array<RetainLevels<A, levels>>
(or similar). If it isn't, the consumer has to call the method with e.g. getAs(5) as RetainLevels<A, 5>
.
Does anyone have any suggestions of how to improve what I currently have to solve (one of) the above problems?
P.S. Kudos to @titian-cernicova-dragomir from who's answer I derived what I currently have
Edited
Corrected mistake as pointed out by @jcalz and added an example for "dynamic return type" use case based on the type defined.