Home > Software design >  How to fix 'Type is missing properties from type' with generic object?
How to fix 'Type is missing properties from type' with generic object?

Time:09-16

Consider a library which exports a run function like below:

runner.ts

export type Parameters = { [key: string]: string };
type runner = (args: Parameters) => void;

export default function run(fn: runner, params: Parameters) {
    fn(params);
}

And consider the following code in a separate file: index.ts

import type { Parameters } from "./runner.ts";
import run from "./runner.ts";

type CustomParams = { hello: string };

function logGenericArgs(args: Parameters): void {
    console.log(args);
}

function logHelloFromArgs(args: CustomParams): void {
    console.log(args.hello);
}

run(logGenericArgs, { abc: "123" });
run(logHelloFromArgs, { hello: "123" });  /* Argument of type '(args: CustomParams) => void' is not assignable to parameter of type 'runner'.
  Types of parameters 'args' and 'args' are incompatible.
    Property 'hello' is missing in type 'Parameters' but required in type 'CustomParams'. ts(2345)
*/

Why does TypeScript complain about the different types, when they're perfectly compatible with each other? From my understanding, type Parameters is a generic object with string keys and string values; CustomParams's "hello" key perfectly fits the Parameters's type signature.

How can I make the code in the "runner" library to accept a generic object type, and work nicely with other types that are compatible?
I do not want to use type unknown or any, as that's basically useless. I want the call signature of the run function to express that args is an object, but I do not want to limit the args' type to that specific signature only.
I also do not want to specify the hello key in type CustomParams as optional, because that key should not be optional in the usage of the type CustomParams - and I do not want to add the key hello in type Parameters because it is not required in every use case of the "runner" library.

CodePudding user response:

TypeScript doesn't know that params is supposed to be the parameters of fn, but you can easily fix that but adding a generic parameter to associate the two:

type Params = { [key: string]: string };
type runner<P extends Params = Params> = (args: P) => void;

function run<P extends Params>(fn: runner<P>, params: P) {
    fn(params);
}

Now when you call your custom params function:

run(logHelloFromArgs, { hello: "123" });

It's inferred as

function run<{
    hello: string;
}>(fn: runner<{
    hello: string;
}>, params: {
    hello: string;
}): void

which is what we want.

Playground

  • Related