0

I'd like to write this function in typescript:

const pick = (obj, keys) => {
  if (!Array.isArray(keys)) keys = keys.split(',')
  return keys.reduce((acum, key) => (acum[key] = obj[key], acum), {})
}

const o = {
  a: 1,
  b: 2,
  c: 3
}

console.log('o[a,c]:', pick(o, 'a,c'))        // { a: 1, c: 3 }
console.log('o[a,c]:', pick(o, ['a', 'c']))   // { a: 1, c: 3 }

I already saw this answer which seems like a good starting point but I can't figure out how to convert the string to a K[].

Or can I just somehow tell Typescript to trust me, and refrain from checking types?

opensas
  • 60,462
  • 79
  • 252
  • 386

2 Answers2

3

Typescript compiler doesn't know what your string will be when you're evaluating it with the split, therefore you must force K[] on it and that will return all the properties of T.

Based on your desired usage, only the second one is viable for getting the desired types.

// i changed the "a" property to a string
const o = { a: 'hello', b: 2, c: 3 };

// <T, K extends keyof T> - here you assign the generics
// T - will be used on "obj" parameter so it can inherit it's properties
// K - will be a property of T
// I typed the "keys" parameter with "string" (to respect your first usage) and K[] (an array of K properties , for the second one)
// At last, we want the function to return K props of T, we have the Pick construct for that.
const pick = <T, K extends keyof T>(obj: T, keys: string | K[]): Pick<T, K> => {
    if (!Array.isArray(keys)) keys = (keys as string).split(',') as K[]; // we know that "keys" is a string, so we'll force the type on it, and we'll force K[] on the .split result, this will return all types from T.
    return keys.reduce((acum, key: K) => (acum[key] = obj[key], acum), {} as T ); // here we mark the accumulator as T, so we know what properties are used.
};

let p1 = pick(o, 'a,c'); // { a: string , b: number, c: number } - You'll get all the keys from obj
let p2 = pick(o, ['a','c']); // { a: string , c: number }
darklightcode
  • 2,738
  • 1
  • 14
  • 17
  • great answer, a couple of questions: - does type Pick represents an object with a subset of the properties of T? - `(keys as string)` is this how you cast in typescript? – opensas Jun 14 '19 at 15:09
  • `Pick` will create a new type based on `K` values under the condition that they exists in `T`, read about the [utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html), they are very handy, and if you want more give a read to [@basarat](https://stackoverflow.com/users/390330/basarat) 's [TypeScript Deep Dive](https://basarat.gitbooks.io/typescript/) – darklightcode Jun 14 '19 at 17:25
  • As of `(keys as string)`, it's rather forcing a type upon a variable/function/anything. I used it in my code because the compiler expects the `keys` to be a `string or K[]`, but when we reach the `.split` method, the typecheck knows that `.split` is a method that can be found on a `string` type and that is ok, but it will fail when it checks against the `K[]` because this type is not a string, therefore, using `someVar as SomeType` you tell the typescript compiler that "YOU_KNOW_BETTER" and that is definately the type you want to check against, and it will honor your type. – darklightcode Jun 14 '19 at 17:38
1

You can have use union

const pick = (obj: Object,  keys: string[] | string) => {
....
}
Potato
  • 770
  • 1
  • 8
  • 18