简体   繁体   中英

Specific Pick implementation in TypeScript

I am running into an interesting TypeScript situation here. Let's say I have a long interface like this:

interface Example {
  foo: string
  bar: number
  baz: boolean
  abc: string[]
  def: number
  xyz: boolean
  /* ... and more ... */
}

Then, I would want to have a pick-like function to send some of those values forward, like this:

const getRequestValuesToSend = (example: Example) => {
  type ExampleKey = keyof Example
  const keysToSend: ExampleKey[] = [
    'foo',
    'bar',
    'xyz'
  ]
  const payload = {} as Partial<Example>
  keysToSend.forEach((key) => {
    payload[key] = example[key] as any /* ???? */
  })
  return payload
}

I want to know how to get rid of the any up there.

PS: this was originally already solved by without using any by using Object.fromEntries and I am sure I could use a generic Pick implementation from here . Nonetheless, I'm still very curious as how/if this is possible in this current form. Thanks!

I'm not sure there is a way to do what you want. There are some things that can get you part of the way there. For example, you could const the array and use that to derive a type of the specifics keys.

const keysToSend = [
     'foo',
     'bar',
     'xyz'
] as const;

// 'foo' | 'bar' | 'xyz'
type SelectedKeys = typeof keysToSend[number];

You can then use this to "Pick" from Example and have a better description of the resultant type

const payload = {} as Pick<Example, SelectedKeys>
// The type of payload is
// {
//   foo: string,
//   bar: number,
//   xyz: boolean
// }

This object will correct understand and type usage when the key is known. For example,

// These are type checked and valid
payload['foo'] = example['foo'];
payload['bar'] = example['bar'];

// Below would be an error as the properties are not compatible.
// payload['xyz'] = example['foo'];

// Below would be an error as it is not one of the "selected" keys
// as long as --noImplicitAny or "strict" is true
// payload['baz'] = example['baz'];

In the forEach , however, key will correctly be typed as 'foo' | 'bar' | 'xyz' 'foo' | 'bar' | 'xyz' 'foo' | 'bar' | 'xyz' but this does not help so much. When accessing a property using key we would know the read type would be either string | number | boolean string | number | boolean string | number | boolean but when writing, we still can't be sure what we are writing to. So while this code is valid,

let a = payload[key]; // string | number | boolean
let b = example[key]; // string | number | boolean
a = b;

The following code is not

payload[key] = example[key];

The compiler is not smart enough to realise that in this case since key is used on the left and right and the types have the same properties so this has to be compatible. Since key refers to multiple property types, it is not safe to write to. If the SelectedKeys all referred to the same type of property, then this would work.

So in this case I'd be happy to use any , mark it as an exception and be content that the function provides safety externally. TypeScript is ueful in that we can 'cheat' in exceptional circumstances to keep the code simple and then wrap in something that is strict, safe and typed well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM