TypeScript set type for array of objects causes problem when sorting with localCompare

Issue

I got a simple function with an array of objects. On this array there will always be at least one string property.

In the body I want to dinamically be able to select that string by key and order with localCompare.

The problem is that the properties can have numbers and when doing localCompare TS complains.

;(() => {
  type entries = {
    [key: string]: string | number
  }


  const data: entries[] = [
    { key: 'bbb', number: 1 }, 
    { key: 'aaa', number: 2}
  ]
  // Let's imagine index comes as a parameter
  const index = 'key'
  const order: entries[] = data.sort((a, b) => a[index].localeCompare(b[index]))

  console.log(order)
})()

Link to TS playground

Solution

Based on your comments, I think you’re saying that you know that a[index] will always be a string because of logic in your code, but the problem is that TypeScript doesn’t know that (because the object signature says it can be a string or a number).

To reassure TypeScript, you have at least a couple of choices:

Use a type assertion function:

function assertIsString(value: any): asserts value is string {
   if (typeof value !== "string") {
       throw new Error(`Expected a string, but got ${typeof value}`);
   }
}

Then:

const order: entries[] = data.sort((a, b) => {
    const avalue = a[index];
    const bvalue = b[index];
    assertIsString(avalue);
    assertIsString(bvalue);
    return avalue.localeCompare(bvalue);
});

Updated playground

This also has the advantage of giving you an explicit error if your code fails to ensure that the property name is only the name of a string property.

Use a type assertion

If you don’t want the (very minimal) overhead of the assertion function, you can just override TypeScript:

const order: entries[] = data.sort((a, b) => (a[index] as string).localeCompare(b[index] as string));

Updated playground

Both

If you know your objects are homogenous, you can combine the two approaches, for instance using the assertion function just on the first entry:

if (data.length > 1) {
    assertIsString(data[0][index]);
}
const order: entries[] = data.sort((a, b) => (a[index] as string).localeCompare(b[index] as string));

Updated playground

That way you get a nice explicit error, but you don’t have the overhead of checking during the sort.

Answered By – T.J. Crowder

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published