So I have the following snippet
interface base {
name: string;
}
interface base2 {
title: string;
}
const func1 = (arg: base) => {
return arg.name;
}
const func2 = (arg: base2) => {
return arg.title;
}
const formatter: Record<string, (arg: base | base2) => string> = {
format: func1,
format2: func2
}
formatter["format"]({ name: "test" });
The two functions have different argument types and I am trying to invoke them as a result of the return value of the object.
But I am getting a type error on the functions
Is there a way to abstractly call the functions and not produce type errors
CodePudding user response:
(arg: base | base2) => string
is a function that can handle both a base
and a base2
argument. func1
and func2
can't do this, they can handle either base
or base2
, so a call formatter["format"]({ title: "test" });
is valid, but will cause func1
to misbehave (in this case return undefined
when it should return string
)
You probably want a union of functions ((arg: base2) => string) | ((arg: base) => string)
. This will let you assign func1
or func2
to the properties of formatter
but since an arbitrary key can be either function (and TS doesn't know which one) you can't call formatter["format"]
with either base
or base2
, you need to call with something that would be valid for either function signature, so the argument would have to be the intersection base & base2
:
const formatter: Record<string, ((arg: base2) => string) | ((arg: base) => string)> = {
format: func1,
format2: func2
}
formatter["format"]({ name: "test" });// Not ok, what if format is (arg: base2) => string)
formatter["format"]({ title: "test" });// Not ok, what if format is (arg: base) => string)
formatter["format"]({ title: "test", name: "" });// Ok, either function will get what it needs
Now this is probably not what you want. If the keys are known and you are using the type annotation just to constrain that all properties have one of the known function signatures, you could use the satisfies
operator in typescript 4.8 instead. This will check that the object respects the object type while preserving the actual property types:
const formatter = {
format: func1,
format2: func2
} satisfies Record<string, ((arg: base2) => string) | ((arg: base) => string)>
formatter["format"]({ name: "test" });// Ok, actual type is known
Or if you are not using a version that supports satisfies
yet you can use a function to do a similar constraint: Playground Link