Search code examples
typescript

TS Mapped Type: Conditionally remove certain key base on value


I'm looking for a solution to produce a mapped type that omit certain key base on the mapped key's value.

Concrete example:

I have an option: Options struct, where Options is an union type of shape:

{ type: DataType } or { type: DataType, params: DataParam }

Some elements of the union have params key and some don't. I want to produce such union utilizing mapped type. Currently I have this working solution (Playground Link):

type DataParams = {
  trade: never;
  trade_bar: {
    interval: number;
    intervalUnit: "ms" | "s" | "m" | "ticks" | "vol";
  };
};


type DataTypes = keyof DataParams;

type OptionsForDataType<T extends DataTypes> = {
  type: T;
} & (DataParams[T] extends never
  ? {}
  : {
      params: DataParams[T];
    })


type Options = {
  [DataType in DataTypes]: OptionsForDataType<DataType>;
}[DataTypes];

But I'm not entirely satisfied with the ugly intersection + extends trick. Is there a better way to express such intention?

I was under the (probably wrong) impression that when { key: never }, that key is effectively excluded from the struct, since nothing is subtype to never, nothing can be assigned to that key. Turns out this is not the case, which looks strange to me.


Solution

  • As of TypeScript 4.1, you can use Key Remapping to remove certain keys based on a conditional type.

    The conditional type must be used to remap the key. The key will be excluded if the conditional type returns never.

    type RemoveCertainKey<T> = {
      [K in keyof T as CONDITION]: T[K]
    }
    
    // CONDITION is a conditional type that can return either the original key,
    // a rewritten key, or never if you want to exclude this property
    

    Here's a playground link to demonstrate how it works. As you can see, condition can be based either on the key or on the value to exclude or keep certain properties.