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.