Christoffer
Christoffer

Reputation: 492

Sort array by multiple object properties in javascript

var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

I want to sort the array by checked and type.id. Im using the following sorting code but it seems to have trouble with "type.id" since my list is not grouped by those if checked are.

sortByPriority(array, ['checked', 'type.id']);

sortByPriority(data, priorities) {
    if (priorities.length == 0 || data.length == 0) {
        return data;
    }
    const nextPriority = priorities[0];
    const remainingPriorities = priorities.slice(1);
    const matched = data.filter(item => item.hasOwnProperty(nextPriority));
    const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

    return this.sortByPriority(matched, remainingPriorities)
        .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
        .concat(this.sortByPriority(remainingData, remainingPriorities));
}

Any ideas on how to sort on the type object as well?

(I haven't been able to find another question with an answer with a generic sorter able to sort based on objects in the array)

Upvotes: 1

Views: 225

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386520

You could use a nested approach for the nested properties and take an array of function for comparing the values.

function sortByPriority(array, keys, fn) {

    function getValue(o, k) {
        return k.split('.').reduce((p, l) => (p || {})[l], o);
    }

    return array.sort((a, b) => {
        var d;
        keys.some((k, i) => d = fn[i](getValue(a, k), getValue(b, k)));
        return d;
    });
}

var array = [{ checked: false, name: "Name", type: { id: 2, tag: "tag" } }, { checked: true, name: "Name", type: { id: 3, tag: "tag" } }, { checked: true, name: "Name", type: { id: 1, tag: "tag" } }];

sortByPriority(array, ['checked', 'type.id'], [(a, b) => b - a, (a, b) => a - b]);

console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

ouni
ouni

Reputation: 3373

Here is a generic solution using composed sorts, in which you can combine an arbitrary number of sorting conditions into one large sort function:

    function to_tuples(array_of_elements) {
      return array_of_elements.map((element, index) => [element, index]);
    }
    function to_elements(array_of_tuples) {
      return array_of_tuples.map((element) => element[0]);
    }
    
    function compose_sorts(sort_functions) {
      const default_sort = (l, r) => l[1] < r[1];
    
      return sort_functions.reduceRight((master_sort, sort_function) => {

        const mixin = (next_sort, left, right) => {
          const result = sort_function(left, right);
          return result === 0 ? next_sort(left, right) : result;
        }
        return mixin.bind(null, master_sort); 
      }, default_sort);
    }

    function sort_checked_to_top(left_tuple, right_tuple) {
      const left = left_tuple[0];
      const right = right_tuple[0];
      if (left.checked !== right.checked) {
        return left.checked ? -1 : 1;
      }
      return 0;
    }
    
    function sort_lower_id_to_top(left_tuple, right_tuple) {
      const left = left_tuple[0];
      const right = right_tuple[0];
      if (left.type.id !== right.type.id) {
        return left.type.id > right.type.id ? 1 : -1;
      }
      return 0;
    }
    
    const master_sort = compose_sorts([
      sort_checked_to_top,
      sort_lower_id_to_top,      
    ]);
    
    var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 25,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

const result = to_elements(to_tuples(array).sort(master_sort));
console.log(result);
    
    
    

Upvotes: 0

Jimenemex
Jimenemex

Reputation: 3166

Overload the sort method on the array and then you can just switch the < and > based on your sort conditions. Note I removed the return 0 if they are equal on the first sort in order to move onto the second sort if the checked values are equal.

var array  = [
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 1,
            "tag": "tag"
        }
    },
    {
        "checked": true,
        "name":"Name",
        "type": {
            "id": 3,
            "tag": "tag"
        }
    },
    {
        "checked": false,
        "name":"Name",
        "type": {
            "id": 2,
            "tag": "tag"
        }
    },
];

function customSort(ob1, ob2) {
    if (ob1.checked > ob2.checked) {
        return 1;
    } else if (ob1.checked < ob2.checked) { 
        return -1;
    }

    // Else sort on the second item
    if (ob1.type.id < ob2.type.id) { 
        return -1;
    } else if (ob1.type.id > ob2.type.id) {
        return 1
    } else {
        return 0;
    }
}

console.log(array);
console.log(array.sort(customSort));

Upvotes: 1

Related Questions