Home > Back-end >  TypeScript: Tell object which keys and values it can have
TypeScript: Tell object which keys and values it can have

Time:11-16

I have a type that represents one of certain constants. Each type also has a specific callback function.

type MyType = 'A' | 'B' | 'C';

// Same for 'B', 'C' etc.
type callbackForA = (result: ResultForA) => void;

ResultForA is a specific object for that belongs to A.

I want to create a TypeScript type or interface (let's call it MagicType) for an object which may have a key for each of these MyType constants. The value for each of these keys can either be true (or implicitly undefined hence "may have") or a maybe a callback function with a specific call signature based on the type.

The following should be a valid MagicType object:

const myObject: MagicType = {
  'A': true | (result: ResultForA) => void,
  'C': true;
}

where B is not configured, and C doesn't take a callback.

The following should be an invalid object for MagicType.

const myObject: MagicType = {
  'A': true | (result: ResultForB) => void;
  'FOO': true
}

because A has the wrong callback function (result: ResultForB) => void and FOO is not a valid option for MyType.

How can you implement such a type?


What I tried, is extending the Result type like this:

type CallbackFunctionVariadicAnyReturn = (...args: any[]) => any

interface MagicType extends Record<MyType, true | CallbackFunctionVariadicAnyReturn>;

Unfortunately, it neither detects superflous keys such as FOO nor does it detect wrong callback functions.


Bonus thought / question:

Is there also a way to tightly couple the callbacks to each MyType value? Maybe a tuple like this?

type MyTypePairs = ['A', (result: ResultForA) => void] | ['B', // ...

CodePudding user response:

type MagicType = {
    'A': boolean | (result: ResultForA) => void,
    'B': undefined,
    'C': boolean,
}

What that does is adds type annotations for each of the three keys, and because TS won't allow any keys other than those that are listed, you can't do the 'FOO' key, and the annotation on the callback means you can't do the (result: ResultForB) => void callback on a.

CodePudding user response:

see TS Playground

// define your types
type ResultForA = { a: string };

interface ResultCollection {
  A: ResultForA;  // can use named type alias
  B: { b: boolean }; // or simply inline your result types
  C: { c: number };
}

// Bonus thought / question:
type ResultFunc<K extends keyof ResultCollection> = (result: ResultCollection[K]) => void;

// Tell object which keys and values it can have
type MagicType = Partial<{[K in keyof ResultCollection]: ResultFunc<K> | boolean}>;


// examples

const good1: MagicType = { // ok
  A: true,
  B: (r: { b: boolean }) => {
    console.log(r);
  },
};

const good2: MagicType = { // ok
  A: (r: { a: string }) => {},
  B: (r: { b: boolean }) => {
    console.log(r);
  },
  C: false
};

const bad1: MagicType = {
  Foo: true, // error
}

const bad2: MagicType = { 
  B: (r: { c: number }) => { // error
    console.log('not a good b');
  },
};
  • Related