Home > database >  How can I "multi sort" an array of objects in Javascript by keys
How can I "multi sort" an array of objects in Javascript by keys

Time:07-21

I'm trying to "enhance" my sorting function for accepting multiple criteria and so doing multi sorting of my array of objects.

Sorry if this is a simple question, or if I committed "newbie" mistakes, but I'm a Frontend developer learning JS foundamentals, and I'm trying to learn how sorting functions working.

Here's my code:

const arrayOfObjects = [
    {
        name: 'John',
        age: 30,
        city: 'New York',
        details: {
            occupation: 'developer',
        }
    },
    {
        name: 'Jane',
        age: 25,
        city: 'Paris',
        details : {
            occupation: 'designer',
        }
    }
];

// This is my main sorting function that I'd like to use for "multi sorting" an array of objects
function sortByKey(criteria) {
            
    return (a, b) => {
        let comparison = 0;

        criteria.forEach(criterion => {
            const varA = (typeof resolvePath(criterion.key, a) === 'string')
            ? resolvePath(criterion.key, a).toUpperCase() : resolvePath(criterion.key, a);
            const varB = (typeof resolvePath(criterion.key, b) === 'string')
            ? resolvePath(criterion.key, b).toUpperCase() : resolvePath(criterion.key, b);

            if (varA > varB) {
                comparison = 1;
            } else if (varA < varB) {
                comparison = -1;
            }

            if(criterion.order === 'desc') {
                comparison = (comparison * -1)
            } 
        });
        
        return comparison;
    };
}

// This function is used to resolve the path of a key in an object
function resolvePath(path, obj) {
    return path.split('.').reduce(function(prev, curr) {
        return prev ? prev[curr] : null
    }, obj || self)
}

The usage of this is:

// MAIN USAGE
console.log( arrayOfObjects.sort(sortByKey([ { key: 'age' } ])) )
console.log( arrayOfObjects.sort(sortByKey([ { key: 'details.occupation', order: 'desc' } ])) )

But I would like to use it some kind of:

console.log( arrayOfObjects.sort(sortByKey([ { key: 'details.occupation' }, { key: 'age', order: 'desc' ])) )

This is because I want to give users the possibility to multi-sort my array in some kind of order, like "Age first, name second, etc..."

Thanks for any useful help, have a nice day!

CodePudding user response:

Create a function which returns the comparator value for each criteria. Loop through the array and call the compare function UNTIL a non-zero value is found. find will stop looking when compareByKey returns 1 or -1.

const sortByKey = (criteria) => (a, b) => {
  let returnValue = 0;
  criteria.find(c => returnValue = compareByKey(c, a, b));
  return returnValue;
}

function compareByKey({ key, order = 'asc' }, a, b) {
  const varA = resolvePath(key, a)
  const varB = resolvePath(key, b)

  let comparison = 0;

  if (varA > varB) {
    comparison = 1;
  } else if (varA < varB) {
    comparison = -1;
  }

  if (order === 'desc') {
    comparison *= -1
  }
  
  return comparison
}

If the find part is confusing or ugly:

Another way would be to reduce the comparator value of each property. But, this doesn't short circuit when a non-zero value is found.

const sortByKey = criteria => 
                    (a, b) => 
                      criteria.reduce((acc,c) => acc || compareByKey(c, a, b), 0)

or

const sortByKey = (criteria) => (a, b) => {
  let comparedValue = 0;

  for (const c of criteria) {
    comparedValue = compareByKey(c, a, b);

    if (comparedValue)
      return comparedValue;
  }
  
  return comparedValue;
}

const arrayOfObjects = [{
    name: 'John',
    age: 30,
    city: 'New York',
    details: {
      occupation: 'developer',
    }
  },
  {
    name: 'Jane',
    age: 25,
    city: 'Paris',
    details: {
      occupation: 'designer',
    }
  },
  // added another designer for testing
  {
    name: 'Jane 2',
    age: 30,
    city: 'Paris',
    details: {
      occupation: 'designer',
    }
  }
];

const sortByKey = (criteria) => (a, b) => {
  let returnValue = 0;
  criteria.find(c => returnValue = compareByKey(c, a, b));
  return returnValue;
}

function compareByKey({ key, order = 'asc' }, a, b) {
  const varA = resolvePath(key, a)
  const varB = resolvePath(key, b)

  let comparison = 0;

  if (varA > varB) {
    comparison = 1;
  } else if (varA < varB) {
    comparison = -1;
  }

  if (order === 'desc') {
    comparison *= -1
  }
  
  return comparison
}

// This function is used to resolve the path of a key in an object
function resolvePath(path, obj) {
  return path.split('.').reduce(function(prev, curr) {
    return prev ? prev[curr] : null
  }, obj || self)
}

console.log(arrayOfObjects.sort(sortByKey([
  { key: 'details.occupation' }, 
  { key: 'age', order: 'desc' }
])))

CodePudding user response:

You can try :

const arrayOfObjects = [{
        id: 3,
        name: 'Jane',
        age: 25,
        city: 'Paris',
        details: {
            occupation: 'designer',
        }
    },
    {
        id: 1,
        name: 'John',
        age: 30,
        city: 'New York',
        details: {
            occupation: 'developer',
        }
    },
    {
        id: 2,
        name: 'Bob',
        age: 25,
        city: 'New York',
        details: {
            occupation: 'developer',
        }
    },
];

let sortByKey = function (a, b, keys) {
    for (let key of keys) {
        if (a[key.key] != b[key.key]) return (a[key.key] > b[key.key] ? 1 : -1) * (key.order == 'desc' ? -1 : 1);
    }
    return 0;
}


let result = arrayOfObjects.sort((a, b) => sortByKey(a, b, [{ key: 'age' }, { key: 'city', order: 'desc' }]));
console.log(result);
  • Related