3

I'm trying to create a function that sorts an array of objects by an object key, but typescript is throwing an error saying the object type is not "any", "number", "bigint" nor an "enum". I tried to constrict the type of the key by number only. It kinda works when I call the function, aka when I try to call the function with a property that is not a number in the argument it shows

Argument of type '"b"' is not assignable to parameter of type 'KeysOfType<{ a: number; b: string; c: number; }, number>'.ts(2345)

but I don't get why in the function itself typescript doesn't recognize that the property is a number, thus throwing me an error. How can I mitigate/solve this issue? Am I approaching the typing incorrectly?

Below what I have with the errors:

type KeysOfType<T, KT> = {
  [K in keyof T]: T[K] extends KT ? K : never;
}[keyof T];

export function sortArrayByKey<T, K extends KeysOfType<T, number>>(
  list: T[],
  propertyKey: K
) {
  // The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.ts(2362)
  // The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.ts(2363)
  return list.slice().sort((a, b) => (a[propertyKey] - b[propertyKey]));
}

// works
sortArrayByKey([{a: 1, b: 'asd', c: 3.0}, {a: 2, b: '123', c: 2.1}], 'a');

// Argument of type '"b"' is not assignable to parameter of type 'KeysOfType<{ a: number; b: string; c: number; }, number>'.ts(2345)
sortArrayByKey([{a: 1, b: 'asd', c: 3.0}, {a: 2, b: '123', c: 2.1}], 'b');

2 Answers 2

2

Here you can find similar question, except this question/answer is about using function constraint instead of number.

TypeScript is able to validate passed propertyKey during function call stage, but is unable to validate b[propertyKey] whether it is a number or not inside function body. a[propertyKey] is infered as T[K], this type knows nothing whether it is a number or not. It is a black box, because you don't even have a constraint for T. TS knows only that K is allowed type for indexing T. Thats all.

In order to make it work, you should apply a constraint to T. For example: T extends Record<PropertyKey, number>. I know, you will say that T might be any object and it is not necessary that all values are numbers. This is why you need to provide a transition function from any object T to T extends Record<string, number>.

Transition function:

const toNumber = <
  Obj extends Record<string, any>,
  Key extends KeysOfType<Obj, number>
>(obj: Obj, key: Key): number => obj[key]

Please notice, that I have used explicit return type number. Without explicit number, return type of this function is T[K]. The good news, typescript allows us to use explicit number and do some checking if number is assignable or not. T

Now, we can write our callback for Array.prototype.sort and our main function:

const callback =
  <Obj,>(propertyKey: KeysOfType<Obj, number>) =>
    (a: Obj, b: Obj) =>
      toNumber(a, propertyKey) - toNumber(b, propertyKey);

const sortArrayByKey = <T, K extends KeysOfType<T, number>>(
  list: T[],
  propertyKey: K
) => [...list].sort(callback(propertyKey))

Playground

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

Comments

0

I think I got this, but I'm not sure if there are other/better solutions:

type KeysOfType<T, KT> = {
  [K in keyof T]: T[K] extends KT ? K : never;
}[keyof T];

export function sortArrayByKey<T, K extends KeysOfType<T, number>>(
  list: T[],
  propertyKey: K
) {
  return list
    .slice()
    .sort(
      (a, b) =>
        (a[propertyKey] as unknown as number) -
        (b[propertyKey] as unknown as number)
    );
}

This is me saying to typescript: "It looks like you still don't understand that type of a[propertyKey] is a number, but I know it's a number so I'm casting it as number".

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.