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: