Search code examples
typescriptfunctionconditional-statementsnotation

How to use the typeof property from bracket notation


I'm learning TS and I'm creating a sort function that sort my array of objects using a params as key using bracket notation. The code above works well, but if I want to extend in the future, i should add every conditions. I want to validate by the type of the value of the key that I want to use.

I try things like the code above and it works to validate but TS keep throwing errors that property/methods inside the IF are not valid because it takes all existing types from every key of my interface and not only the ones that really can go through the condition

if(typeof arr[prop] === "string")
if(arr[prop] instanceof String)
if(arr[prop].constructor.name === "String")

This is the code

  interface Person{
  name: string,
  age: number,
  isAlive: boolean}

const myArr = [
    {name: 'Tomás',
    age: 24,
    isAlive: true},
    {name: 'Kate',
    age: 12,
    isAlive: true},
    {name: 'Brad',
    age: 54,
    isAlive: false},
    {name: 'Angelica',
    age: 64,
    isAlive: true},
]

function myFn(
    arr: Person[],
    orderBy: keyof Person): Person[] {
        if(orderBy === 'name'){
            return arr.sort((a,b) => a[orderBy].localeCompare(b[orderBy]));
        }
        else if (orderBy === "age") {
          return arr.sort((a, b) => a[orderBy] - b[orderBy]);
        } 
        else if (orderBy === "isAlive") {
          return arr.sort(value => value[orderBy] ? -1 : 1)
        }
        else {
            return [];
        }
  }

  const byAlive = myFn(myArr, "isAlive");
  console.log(byAlive)
  const byAge = myFn(myArr, "age");
  console.log(byName)
  const byName = myFn(myArr, "name");
  console.log(byName)

Thanks in advance and sorry for my English, I'm not native :) If I'm not clear with something or you can't understand me, please let me know so I try to write it in a better way.


Solution

  • First of all, Typescript has difficulty narrowing a type through a dynamic property access like:

    if (typeof person[prop] === 'string') person[prop].toUpperCase() // error
    

    You can make this easier for Typescript by assigning the result of person[prop] to a variable, and then using that. For example:

    const value = person[prop]
    if (typeof value === 'string') value.toUpperCase() // works
    

    So then, make just one array.sort call where you save the property value from a and b to separate variables, and then test the types to find the proper comparison.

    For example:

    function myFn(
        arr: Person[],
        orderBy: keyof Person
    ): Person[] {
        return arr.sort((aPerson, bPerson) => {
            const a = aPerson[orderBy]
            const b = bPerson[orderBy]
    
            if (typeof a === 'string' && typeof b === 'string') {
                return a.localeCompare(b)
            }
    
            if (typeof a === 'number' && typeof b === 'number') {
                return a - b
            }
    
            if (typeof a === 'boolean' && typeof b === 'boolean') {
                return a ? 1 : -1
            }
    
            throw Error('unsupported value type')
        })
    }
    

    See playground