Get properties with type K from type where property is optional

Viewed 86

I have a type:

interface IThing {
   foo?: number[]
   bar?: number[]
   baz?: number[]
}

I want to write a generic method to delete the values of any of the properties of type number[] that are empty arrays.

I have used this as my starting point:

type FilteredKeyOf<T, TK> = keyof Pick<T, { [K in keyof T]: T[K] extends TK ? K : never }[keyof T]>

function deleteIfEmpty(value: IThing, key: FilteredKeyOf<IThing, number[]>) {
  const v = value[key]
  if (v && v.length === 0) {
      delete v[key];
  }
}

If foo, bar and baz are not optional it behaves like I expect. How can I account for the optional values?

1 Answers

There are two problems:

  1. Optional properties will be optional in the intermediate { [K in keyof T]: T[K] extends TK ? K : never } type. This means that when you index it you get an undefined as part of those values.
  2. T[K] will include undefined for optional values, so they won't extend TK, only TK | undefined.

What you can do is make everything required in the mapping part, and allow for undefined when filtering:

type FilteredKeyOf<T, TK> = keyof Pick<T, 
    { [K in keyof Required<T>]: T[K] extends TK | undefined ? K : never }[keyof T]>

Full example (playground):

interface IThing {
    otherVal: string;
    undef: undefined;
    optString: string | undefined;
    foo?: number[];
    bar: number[] | undefined;
    baz: number[];
}

type FilteredKeyOf<T, TK> = keyof Pick<T, 
    { [K in keyof Required<T>]: T[K] extends TK | undefined ? K : never }[keyof T]>

function deleteIfEmpty(value: IThing, key: FilteredKeyOf<IThing, number[]>) {
  const v = value[key]
  if (v && v.length === 0) {
      delete value[key];
  }
}

declare const thing: IThing;

deleteIfEmpty(thing, "foo"); // Optional OK
deleteIfEmpty(thing, "bar"); // May be undefined OK
deleteIfEmpty(thing, "baz"); // Required OK
deleteIfEmpty(thing, "undef"); // Edge case I think
deleteIfEmpty(thing, "optString"); // Error
deleteIfEmpty(thing, "otherVal"); // Error
deleteIfEmpty(thing, "nonExistent"); // Error
Related