18

lodash has the pick function which is used as follows:

var object = { 'a': 1, 'b': '2', 'c': 3 };
 
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }

I would like to write a type-safe version of this in typescript.

Usage of this function should be

pick(object, "a", "b")

The goal is to conserve type safety.

Is this possible to achieve?

jcalz
  • 264,269
  • 27
  • 359
  • 360
rony l
  • 5,798
  • 5
  • 35
  • 56
  • 1
    Your pick function seem to have a different signature than the lodash example? What are those second and third parameters (particularly the third)? – CRice Nov 10 '17 at 23:18
  • I just edited this question to make the accepted answer (and indeed all the answers) apply to it. It was originally written where the second argument was something like a callback that returned property values (although it was syntactically invalid, so not sure if that was the point). Changed it to a list of keys. Hope that's okay. – jcalz May 19 '23 at 14:26

7 Answers7

35

Sounds like you might be looking for the Pick type. Would something like this work for you?

function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  const ret: any = {};
  keys.forEach(key => {
    ret[key] = obj[key];
  })
  return ret;
}

const o = {a: 1, b: '2', c: 3}
const picked = pick(o, 'b', 'c');

picked.a; // not allowed
picked.b  // string
picked.c  // number
ethan.roday
  • 2,485
  • 1
  • 23
  • 27
  • Do you have suggestion how to get rid of any type here? – Dan Zawadzki Nov 12 '19 at 16:00
  • 1
    @DanZawadzki that depends. What would be the goal of doing that? As written, the `any` doesn't "seep" out of the implementation - it's just the easiest way to build up an object from nothing without making Typescript unhappy. – ethan.roday Nov 14 '19 at 17:42
  • 2
    In my version I decided to use the partial function. Project configuration often disallow the use of any interface, hence I wonder how to avoid it, with out disabling the rule. `const ret: Partial> = {}` instead of `const ret: any = {};` – Dan Zawadzki Nov 16 '19 at 17:24
  • The only negative I see with that is that you also need to change the return type to `Partial>`. If that's workable for you, though, sounds like a good solution. – ethan.roday Nov 16 '19 at 20:14
  • Is there any library with this pick helper method implemented for us? – Ozymandias Aug 21 '20 at 20:15
  • 3
    @DanZawadzki Based on the example about `unproxify` [in the typescript handbook](https://www.typescriptlang.org/docs/handbook/advanced-types.html), the response would be `const ret = {} as Pick`. – xanatos Nov 22 '20 at 13:41
4

here's mine, using Object.assign()

function pick<T, K extends keyof T>(object: T, keys: K[]): Pick<T, K> {
  return Object.assign(
    {},
    ...keys.map(key => {
      if (object && Object.prototype.hasOwnProperty.call(object, key)) {
        return { [key]: object[key] };
      }
    })
  );
};
bayu
  • 161
  • 1
  • 5
3

With Object.fromEntries (es2019):

function pick<T extends object, K extends keyof T> (base: T, ...keys: K[]): Pick<T, K> {
  const entries = keys.map(key => ([key, base[key]]));
  return Object.fromEntries(entries);
}
Seb D.
  • 5,046
  • 1
  • 28
  • 36
1

The lodash type definitions use Pick to propagate the props, so you don't have to define it manually. Just install them with npm i --save-dev @types/lodash or yarn add -D @types/lodash.

Petr Bela
  • 8,493
  • 2
  • 33
  • 37
0

Here mine, extending @Bayu Karnia answer:

function pick<T extends Record<string | number | symbol, T>, K extends keyof T>(
  object: T,
  keys: K[]
): Pick<T, K> {
  return Object.assign(
    {},
    ...keys.map((key: K) => {
      return { [key]: object[key] };
    })
  );
}
cvss
  • 411
  • 1
  • 4
  • 11
0

Based on Ethan.Roday's answer, I made a few changes. Using Array.reduce and get rid of any type.

export function pick<T, K extends keyof T>(obj: T, keys: K[]) {
    return keys.reduce((acc, val) => {
        return (acc[val] = obj[val]), acc;
    }, {} as Pick<T, K>);
}
Mahmoud
  • 456
  • 3
  • 13
0

Looks like I managed to swap arguments + curry and finally types are good enough. Will be grateful if somebody explains it:

const pick = <T, K extends keyof T>(keys: (keyof T)[]) => <R extends T>(obj: R) => keys.reduce<Pick<R, K>>((acc, key) => {
    (acc as T)[key] = obj[key]
    return acc;
}, {} as Pick<R, K>)
Artem Kislov
  • 1,101
  • 1
  • 7
  • 3