Reputation: 631
I'm calling an api from a server that returns an array of data from a mongo db. The schema of this data contains an ObjectID
that references other models in the database. So there are two schemas, one where the property remains an ObjectID string
and the other is a populated
property where the string
becomes an object. Below is the sample type definition, where prop1
can either be a string
or an object
.
type Base = {
name: string;
description?: string;
prop1: string; // ObjectID
}
type Populated = {
prop1: {
name: string;
description: string;
}
}
With this, I made a conditional type that can determine whether the returned data is populated or not. The type is a generic that accepts either true
or undefined
. If the generic is true
, it returns the Base
type with the prop1
being the type defined in Populated
, else it simply returns the Base
type.
type Schema<T extends true | undefined> = T extends true ? Omit<Base, keyof Populated> & Populated : Base
Then, a function calls the api with the explicit return type (because api call defaults to any
). It accepts an optional parameter that determines whether to populate the ObjectID
s or not.
const callApi = async <T extends true | undefined>(populate?: T): Promise<(Schema<typeof populate> & {_id: string})[]> => {
// example only
return {} as any
}
I tried to get the populated
version of this schema, however the type remains string | { name: string; description: string; }
const test = async () => {
const populated = await callApi(true)
populated.map((data) => data.prop1)
// data.prop1 is type `string | { name: string; description: string; }`,
// should only be `{ name: string; description: string; }`
}
Here is a playground. Thanks for the help!
Upvotes: 1
Views: 523
Reputation: 23825
There are two changes that need to be done:
type Schema<T extends true | undefined> = T extends true
? Omit<Base, keyof Populated> & Populated
: Base;
const callApi = async <T extends true | undefined = undefined>(
populate?: T
): Promise<(Schema<T> & { _id: string })[]> => {
return {} as any;
};
You need to set a default type of undefined
for the generic type T
. When you call callApi
without an argument, the type of T
can not be inferred and inference falls back to the constraint of T
. Since the constraint is a union of true | undefined
, the conditional type distributes over the union and returns a union of both branches.
You also don't want to pass typeof populate
to Schema
since typeof populate
evaluates to a union of T | undefined
which is also distributed in the conditional type. Pass the generic type T
instead.
const test = async () => {
const populated1 = await callApi();
populated1.map((data) => data.prop1);
// ^? prop1: string
const populated2 = await callApi(true);
populated2.map((data) => data.prop1);
// ^? prop1: { name: string; description: string; }
};
Upvotes: 1