I have a data source, let's call it getData()
, that returns objects. Sometimes it returns objects of known type (e.g. Person
, Animal
), but sometimes the returned object has an unknown shape.
(Link to TypeScript Playground)
type Person = { name: string; age: number };
type Animal = { species: string };
/**
* This interface stores all known object types returned by `getData()`.
* I'm storing it as an interface instead of `Person | Animal` because I
* need to store the "code" of a known type (e.g. `"person"` for `Person` type).
*/
interface DataCategory {
person: Person;
animal: Animal;
}
/** Our data source */
const getData: Person | Animal | any = () => {
return {}; // Mocked value
};
Now I wish to write a helper function useData()
to narrow down getData()
's return value. It accepts an optional parameter of type keyof DataCategory
and returns the corresponding type. I wish to make this function return any
if we don't pass a parameter.
const person = useData("person"); // const person: Person
const animal = useData("animal"); // const animal: Animal
const notKnown = useData(); // const notKnown: any
I've tried the following implementation:
function useData<T extends keyof DataCategory>(category?: T) {
const data: any = getData();
return data as T extends undefined ? any : DataCategory[T];
}
const animal = useData("animal");
// ^ const animal: Animal
const notKnown = useData();
// ^ const notKnown: Person | Animal
// However, I want the above to be `const notKnown: any`
That doesn't work because useData()
returned Person | Animal
instead of any
. How can I fix this problem?
CodePudding user response:
By default TS will use the constraint as the type for a type argument it can't infer (and in this case there is no location to infer T
from). You could change this by using any
as the default:
function useData<T extends keyof DataCategory = any>(category?: T) {
const data: any = getData();
return data as T extends undefined ? any : DataCategory[T];
}
You can also use overloads as Michael Rose points out in his answer and they might be easier for future readers to understand.
CodePudding user response:
An alternative to trying to use generics would be to just overload your useData
function and specify the category:
function useData(category: "animal"): Animal;
function useData(category: "person"): Person;
function useData(): any;
function useData(category?: string): any {
return getData();
}
const animal = useData("animal");
// ^ const animal: Animal
const notKnown = useData();
// ^ const notKnown: any