Home > other >  Declare object with keys constrained to be element of object literal
Declare object with keys constrained to be element of object literal

Time:05-06

Suppose I have a literal type defined as:

type MyKeys = 'a' | 'b' | 'c' | 'd'

I'd like to declare a mapped type similar to Record<MyKeys, number>, except I only want to have to fill in a subset of my keys. (In reality, the literal has ~20 values).

I'd like to avoid Partial because it allows a potential value to be specified as undefined. I'd like to help developers ensure that if they define a value, it's specified. This also helps when dealing with doing Object.entries, because it gets rid of handling undefined for the values.

The scenario here is that we have a variety of plugins that require configuration, but do not require all fields. So something like:

// Use another type instead of Partial<Record<...>>
type ConfigType = Partial<Record<MyKeys, number>>

const PluginA: ConfigType = {
  a: undefined, // Would be legal with Partial, but I'd like to block this
  b: 25
};

const PluginB: ConfigType = {
  c: 13,
  d: 52,
};

const PluginC: ConfigType = {
  a: 12,
  d: 42,
};

CodePudding user response:

Like my comment hints at, you can't really make a feasible solution so you can type it like this and have it validate it correctly:

const conf: ConfType = { ... };

But what you can do is have a function to do that for you:

const conf = checked({ ... }); // errors at COMPILE TIME if invalid

Let's see how we can do this:

type ConfInvalidError = [{ error: "Cannot pass undefined." }];

type ValidConf<C> = C extends Partial<Record<MyKeys, number>> ? undefined extends C[keyof C] ? ConfInvalidError : C : ConfInvalidError;

function checked<C>(conf: ValidConf<C>): C { return conf as C; }

TypeScript will infer C, then pass it to ValidConf.

In ValidConf, we check if it's a valid config object. If it extends Partial<Record<MyKeys, number>>, and doesn't include undefined in the values, then it will give back C, otherwise it gives a helpful error message.

Test this solution out in the playground below:

Playground

  • Related