3

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.

hackape
  • 18,643
  • 2
  • 29
  • 57
  • Not sure if better (instead of `never` checks if key `DataParams` map and simplifies the `Options`) https://www.typescriptlang.org/play?#code/C4TwDgpgBAIghsOAVcEDOUC8UBEwBOcAJhDlAD64HEQD6ARnPjgNwBQoksCcACk3AC2GbAG82UKNRIMmALijjJkgJYA7YBHwA3OABsFagK6D6W9sqjrNO-QFU1K4ApzCylHGne5B3vCoBjAGsvClxtAHs9VgkoAF92BLYOVCgAeTBgFQi1NAAxCPx4RBRIAB4kKAgAD001Igxi5FQ0AD4sKEqauoaoIIgQCIAzbkR+QmFYgH5FKVQFJAAaKDABYQUm8aE0AG0kAF142IVROcgFo5SuDKyckXTM7NyCop5SiDKm97a2IA – Aleksey L. Nov 09 '20 at 08:38
  • @AlekseyL. Thanks for responding. But I still want to leave `{ trade: never }` as-is, cus in my use case I want to keep things in one place. I've edit my question to disambiguate. – hackape Nov 09 '20 at 09:36

1 Answers1

3

I'm kinda embarrassed that I didn't do my Google Diligence™️ thoroughly. Search "mapped type exclude never value" keywords and it takes me to microsoft/TypeScript#23199, where people has the same doubt post the issue and got a solution.

So the digest to that issue thread is, my doubt is legit, the TS team also think { key: never } should exclude the key, however due to some technical difficulty they cannot implement it.

Workaround is to create a utility type that filters desired keys, and apply it as an intermediate step. Solution (Playground Link), taken from that issue and adapted to my use case, goes like:

// Utilities:
type FilteredKeys<T> = { [P in keyof T]: T[P] extends never ? never : P }[keyof T];
type ExcludeNeverFields<O> = {
  [K in FilteredKeys<O>]: O[K]
}

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

type DataTypes = keyof DataParams;

type Options = {
  [DataType in DataTypes]: ExcludeNeverFields<{
    type: DataType;
    params: DataParams[DataType];
  }>
}[DataTypes];
hackape
  • 18,643
  • 2
  • 29
  • 57