1

Let's say I want to write a sortBy function, that takes a list of Ts and a key of T to sort the list by.

To properly work I want the key to only accept keys of T that are numeric.

I have this, but I don't know how to restrict Key so that T[Key] refers to a number:

const sortBy = <T, Key extends keyof T>(items: T[], key: Key) { 
  // impl
}

I played around with this, but could not get it to work:

type NumericAttributesOf<T> = {
  [K in keyof T]: T[K] extends number ? T[K] : never
}

Update:

based on the answer to this question this is what I ended up with:

type KeysMatching<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]

function sortBy<T>(items: T[], key: KeysMatching<T, number>): T[]
function sortBy<K extends PropertyKey>(
  items: Record<K, number>[],
  key: K,
): Record<K, number>[] {
  return [...items].sort((a, b) => a[key] - b[key])
}
DiegoFrings
  • 3,043
  • 3
  • 26
  • 30

2 Answers2

1

You can use the following solution

const sortBy = <T, Key extends keyof T>(items: T[], key: T[Key] extends number ? Key : never) => { 
  // impl
}

sortBy([{
  name: 'Test',
  age: 20
}], 'name') // ERROR

sortBy([{
  name: 'Test',
  age: 20
}], 'age') // Ok

Explanation:

This code stores the type of the second parameter in Key. It then restricts Key to keys where T[Key] is a number.

anut
  • 481
  • 1
  • 11
  • Hmmm. It does not work for me. I think the `NumericAttributesOf` is wrong. `keyof NumericAttributesOf<{ name: string; age: number }>` gives me `name | age`, where it should only be `age`. – DiegoFrings Jul 18 '22 at 15:37
1

There are two issues with

type NumericAttributesOf<T> = {
  [K in keyof T]: T[K] extends number ? T[K] : never
}
  1. T[K] extends number ? T[K] : never selects value type while you're looking for key type, so it should be [K in keyof T]: T[K] extends number ? K : never
  2. The result of mapped type is an "object", you need to use lookup to extract union of allowed keys.

Here's a working example:

type NumericKeys<T> = { 
  [K in keyof T]: T[K] extends number ? K : never 
}[keyof T]

const sortBy = <T, Key extends NumericKeys<T>>(items: T[], key: Key) => { 
  // impl
}

sortBy([{ str: 'str', num: 1}], 'str') // Error: Argument of type '"str"' is not assignable to parameter of type '"num"'
sortBy([{ str: 'str', num: 1}], 'num') // OK

Playground

Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
  • That seems to work quite well! Thanks a lot. In the implementation I try to access the key of an item, but instead of giving me `number` as a type, I get the `Key[T]`, which I also get without the whole restriction to numeric keys only :\ – DiegoFrings Jul 18 '22 at 16:32
  • In the end I found a solution based on the answers to this question: https://stackoverflow.com/questions/61764867/typescript-accept-all-object-keys-that-map-to-a-specific-type – DiegoFrings Jul 18 '22 at 19:38
  • Thanks Aleksey, will accept your answer! – DiegoFrings Jul 18 '22 at 19:41