Home > database >  Generic type from keyof that is an object
Generic type from keyof that is an object

Time:12-17

I'd like to type a generic function to only include values that are of type object (and ignore/disallow the other ones).

This is to only allow updating objects within a map. Is it possible? I haven't been able to find a solution.

enum SKey {
  key1 = "key1",
  key2 = "key2",
}

type SItem = {
  [SKey.key1]: boolean;
  [SKey.key2]: {
    attr1: string;
    attr2: boolean;
  };
};

const mapDB: SItem = {
  [SKey.key1]: true,
  [SKey.key2]: {
    attr1: "hello",
    attr2: true,
  },
};

export function updateDbItem<K extends keyof SItem>(
  key: K,
  newFields: Partial<SItem[K]>
) {
  const item: SItem[K] = mapDB[key];
  mapDB[key] = { ...item, ...newFields };
}

But obviously I get the following error

error TS2698: Spread types may only be created from object types.

mapDB[key] = { ...item, ...newFields };
              ~~~~~~~~

Thank you for your help!

CodePudding user response:

You can use a mapped type to derive a union of only the keys whose values are object types.

In the code you've shown, this evaluates to only SKey.key2, but if you add more entries to SItem, then it will also include other keys that meet the criteria.

Here's an example:

TS Playground

type SItemObjectKey = keyof {
  [
    K in keyof SItem as SItem[K] extends Record<string, unknown>
      ? K
      : never
  ]: unknown;
};

export function updateDbItem<K extends SItemObjectKey>(
  key: K,
  newFields: Partial<SItem[K]>
) {
  const item: SItem[K] = mapDB[key];
  mapDB[key] = { ...item, ...newFields }; // Ok now!
}

See also: the utility type Record<Keys, Type>

  • Related