Home > front end >  Typescript - How to get keys behind specific values from an object
Typescript - How to get keys behind specific values from an object

Time:07-06

I have a super simple use-case. I want have a function pluckOnlyStringValues on which I pass an object obj and a key and I want to ensure that I can pass only such keys whose values are string. In such a way that pluckOnlyStringValues always returns string.

For the goal I'm trying implementing a type helper PickKeysByValue, but it does not seem to work...

type PickKeysByValue<T extends object, ValueTypes> = {
  [K in keyof T]-?: T[K] extends ValueTypes ? K : never;
}[keyof T];

// Working
type GetKeysWithStringValues = PickKeysByValue<
  { a: string; b?: string; c: number | undefined; d: () => 4 },
  string
>;
// Working
type GetStringValues = { a: string; b?: string; c: number | undefined; d: () => 4 }[GetKeysWithStringValues]

// Not working
const pluckOnlyStringValues = <O extends { a: string }>(
  obj: O,
  key: PickKeysByValue<O, string>,
): string => {
  return obj[key];
};

CodePudding user response:

In pluckOnlyStringValues implementation, O is unknown, so PickKeysByValue<O, string> is unknown too. TypeScript is not clever enough to determine that obj[key] is always of type string

You could add a function overload which is compatible with your current declaration and with the implementation.

function pluckOnlyStringValues<O extends { a: string }>(
  obj: O,
  key: PickKeysByValue<O, string>,
): string
function pluckOnlyStringValues(
  obj: Record<string, string>,
  key: string,
): string {
  return obj[key];
};

TypeScript playground

CodePudding user response:

If you change the constraint of O to Record<string, any>, TypeScript will know that O is indexable with a string.

const pluckOnlyStringValues = <O extends Record<string, any>>(
  obj: O,
  key: PickKeysByValue<O, string>,
): string => {
  return obj[key];
};

CodePudding user response:

Based on your description,..

You could do this with a helper type, StringKeys, this would then just return Keys that are string types.

const test = {
  aNumber: 12,
  aString: 'ABC'
};

type StringKeys<T extends object> = {
  [K in keyof T]: T[K] extends string ? K : never
}[keyof T];

function pluckString<T extends object>(obj:T, key:StringKeys<T>) {
  return obj[key];
}

pluckString(test, 'aString')  //ok
pluckString(test, 'aNumber'); //error

Playground with Return

  • Related