Reputation: 15372
I have a function that can return a couple of different types and I want to specify the return using an argument:
type returnTypes = 'string' | 'object';
function getValue(returnType: returnTypes): string | Record<string, string> {
if(returnType === 'object') {
return { my: 'return value' }
}
return 'my return value';
}
interface IData {
myObjectValue: Record<string, string>;
myStringValue: string;
}
const data: IData = {
myObjectValue: getValue('object'),
myStringValue: getValue('string'),
}
The assignment to data
gets me the following errors:
Type 'string | Record<string, string>' is not assignable to type 'Record<string, string>'. Type 'string' is not assignable to type 'Record<string, string>'.
Type 'string | Record<string, string>' is not assignable to type 'string'. Type 'Record<string, string>' is not assignable to type 'string'
presumably because TypeScript thinks the return value of getValue
is wider than the types defined in my interface.
returnType
argument, which of the two return types I will receive?(Or is there a more canonical way to do this, eg just separate into functions, if so what/how?
Upvotes: 0
Views: 3361
Reputation: 4194
You can use overloads...
function getValue(returnType: 'object'): Record<string, string>
function getValue(returnType: 'string'): string
function getValue(returnType: 'string' | 'object'): string | Record<string, string> {
if(returnType === 'object') {
return {my: 'return value'}
}
return 'my return value';
}
interface IData {
myObjectValue: Record<string, string>;
myStringValue: string;
}
const data: IData = {
myObjectValue: getValue('object'),
myStringValue: getValue('string'),
}
Or a generic with conditional return type but will have to make assertions in the implementation while returning...
type returnTypes = 'string' | 'object';
function getValue<T extends returnTypes>(returnType: T): T extends 'string' ? string : Record<string, string> {
if(returnType === 'object') {
return {my: 'return value'} as any
}
return 'my return value' as any;
}
interface IData {
myObjectValue: Record<string, string>;
myStringValue: string;
}
const data: IData = {
myObjectValue: getValue('object'),
myStringValue: getValue('string'),
}
Upvotes: 2
Reputation: 17382
The typescript compiler does not infer the returntype of the function by the value of parameters you are passing in. That would only be evaluated at runtime.
So it always assumes the returned type to be string | Record<string, string>
. What you can do, is using a Type Assertion at the receiving site, to tell typescript, what type to expect.
type returnTypes = 'string' | 'object';
function getValue(returnType: returnTypes): string | Record<string, string> {
if(returnType === 'object') {
return { my: 'return value' }
}
return 'my return value';
}
interface IData {
myObjectValue: Record<string, string>;
myStringValue: string;
}
const data: IData = {
myObjectValue: getValue('object') as Record<string, string>,
myStringValue: getValue('string') as string,
}
Of course, such type asserations are not a guarantee, that this will work at runtime. Because if your getValue
method has an error and you return a string
even if the parameter is object
, this will lead to follow up errors, when you try to access myObjectvalue.my
The "cleanest" solution of would probably be, to define two distinct functions, one returning a string
the other one returning a Record<string, string>
Upvotes: 1