3

I'd like to come up with some conditional to extract only the properties from an object whose value is an array.

For example:

type Person = {
   name: string
   addresses: Address[]
   age: number
   phoneNumbers: PhoneNumber[]
}

PullOutArrays<Person> => {
   phoneNumbers: PhoneNumber[]
   addresses: Address[]
}

I attempted something like this to no avail:

type PulledOutArrays<T extends Record<string, unknown>> = {
  [K in keyof T]: T[K] extends unknown[] ? T[K] : never
}

3 Answers 3

6

Cribbing from this GitHub issue:

type FilteredKeys<T, U> = { [P in keyof T]: T[P] extends U ? P : never }[keyof T];

type FilteredProperties<T, U> = { [K in FilteredKeys<T, U>]: T[K]; };

type PullOutArrays<T> = FilteredProperties<T, unknown[]>;

type Person = {
  name: string
  addresses: string[]
  age: number
  phoneNumbers: number[]
}

type PersonKeys = FilteredKeys<Person, unknown[]>;

// "addresses" | "phoneNumbers"

type PersonArrays = PullOutArrays<Person>;

// {
//    addresses: string[];
//    phoneNumbers: number[];
// }

As you may have seen from your original attempt, what you end up with after mapping the conditional:

{ [P in keyof T]: T[P] extends U ? P : never }

Is an interface containing all the property keys, but with the non-array types changed to never, apparently because TS does not initially check what the types assigned to property keys are. By adding an index on the end, you force the compiler to read the value of the property and ultimately omit it from the result.

Using the union which is generated, you can then create a new mapped type from that union on the original interface.

Sign up to request clarification or add additional context in comments.

3 Comments

Got it, thank you for the clear explanation. Definitely a more elegant solution than the one I came up with
Only downside I'm dealing with now is apparently this doesn't work with Generics?
Yes I can see that now on the GitHub page - I haven't got a workaround for that at the moment I'm afraid. Again, mostly just cribbing from what I've seen in the wild.
2

Or if you are using TS 4.1+, a more direct approach could be to remove keys by asserting the key as never based on the value type:

type OnlyArraysPerson = {
  [K in keyof Person as Person[K] extends unknown[] ? K : never]: Person[K]
}

To be more generic:

type OnlyArrays<T> = {
 [K in keyof T as T[K] extends unknown[] ? K : never]: T[K]
}

type ArrayPerson = OnlyArrays<Person>

Playground

1 Comment

This appears to be the correct solution nowadays
1

Came to a solution by following this answer:

type KeysOfType<T, U, B = false> = {
  [P in keyof T]: B extends true
    ? T[P] extends U
      ? U extends T[P]
        ? P
        : never
      : never
    : T[P] extends U
    ? P
    : never
}[keyof T]

type PickByType<T, U, B = false> = Pick<T, KeysOfType<T, U, B>>

type PickArrays<T> = PickByType<T, unknown[]>

@lawrence-witt's answer is more elegant and should probably be used.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.