Skip to content
Advertisement

Typescript Pick optionnal unset variable

I’m trying to use the “pick” function of typescript to get all the possible values of my objects. My objects have optional attributes so they are not necessarily set

const foo = [
    {
      name: 'index',
    },
    {
      name: 'id',
      type: 'number',
    },
    {
      name: 'error',
    },
  ] as const

type ElementArg<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ElementType>
  ? Pick<ElementType, 'name' | 'type'>
  : never

type result = ElementArg<typeof foo>
//result : {name: "index" | "id" | "error"}
//expected : {name: "index" | "id" | "error" type: "number" | "undefined"}

and I also try to convert “type” attribut to a type with :

type typeDecoder<T> = T extends 'number' ? number
  : T extends 'number[]' ? number[]
    : T extends 'string' ? string
      : T extends 'string[]' ? string[]
        : T extends 'boolean' ? boolean
          : T extends 'undefined' ? undefined
            : never;

but I think there is a better way to do it and I don’t know where to use my function

Advertisement

Answer

I managed to get something to work:

type ElementArg<T extends ReadonlyArray<unknown>, R extends {} = {}> = T extends readonly [infer First, ...infer Rest]
  ? ElementArg<Rest, {
    [K in keyof First | keyof R]:
      K extends keyof R
        ? K extends keyof First
          ? R[K] | First[K]
          : R[K] | "undefined"
        : K extends keyof First
          ? First[K]
          : never
  }>
  : R;

The main idea is that we loop through each of the elements in the tuple and then we add to the result we accumulate.

T extends readonly [infer First, ...infer Rest]

Here we get the first element and the rest as a tuple. Next is this big chunk:

  ElementArg<Rest, {
    [K in keyof First | keyof R]:
      K extends keyof R
        ? K extends keyof First
          ? R[K] | First[K]
          : R[K] | "undefined"
        : K extends keyof First
          ? First[K]
          : never
  }>

We use ElementArg again on the remaining elements, and then the long complicated mapped type correctly adds this element’s contents to the results.

The logic is like this:

  • For each key in the element or result

  • If the key is a key of the result

    • If the key is a key of the element

      • Add the key’s value to the result
    • Otherwise

      • Add "undefined" to the result
  • Or else if the key is a key of the element

    • This is a new key we add to the result

And finally if the first step where we attempt to get the first element does not work, that means T is empty and we are done, so we return R.

Playground

Advertisement