0

So I've recently come across a problem I can't seem to wrap my head around.

Let's say I've defined an array of objects in javascript, and want the user to be able to choose what value to sort that array by. I have no problem sorting the array when I know the depth, as it would be something along the lines of

array = array.sort(function (a, b) {
    return b["foo"]["bar"] - a["foo"]["bar"];
});

but I don't exactly know how to go about doing this when the depth is unknown. I've attempted putting the keys in a string and using eval(), but that does not seem to work.

I've set up a quick example on JSFiddle to better demonstrate what I mean http://jsfiddle.net/DakotaSv/c35bj02w/2/

If anyone could think of a solution, I'd be grateful!

(Thanks to PatrickD, here is the working JSFiddle for anyone who may find it useful!)

Dakota S
  • 13
  • 1
  • 3
  • You're probably looking for some sort of *"recursive search in objects"* to find the keys ? – adeneo Oct 15 '15 at 19:20
  • It might be better to create an object structure to handle this kind of complexity rather than just a one-liner. – ryuu9187 Oct 15 '15 at 19:22

5 Answers5

4

Here is a working solution. It uses ES6-Syntax, but this should not be a problem:

'use strict'

var users = [
  {'Name' : 'John', 'Attributes' : {'Age' : 5, 'Height' : 1.5, 'Clothes' : {'Shirts' : 5, 'Pants' : 8}}}, 
  {'Name' : 'Andrew', 'Attributes' : {'Age' : 9, 'Height' : 1.8, 'Clothes' : {'Shirts' : 2, 'Pants' : 5}}}, 
  {'Name' : 'Lucifer', 'Attributes' : {'Age' : 11, 'Height' : 1.3, 'Clothes' : {'Shirts' : 9, 'Pants' : 4}}}
];

function sort(valuePath, array){
  let path = valuePath.split('.')  

  return array.sort((a, b) => {
     return getValue(b,path) -  getValue(a,path)    
  });

  function getValue(obj, path){
    path.forEach(path => obj = obj[path])
    return obj;
  }
}


console.log(sort('Attributes.Height', users))
console.log(sort('Attributes.Clothes.Shirts', users))

The output is correct.

PatrickD
  • 1,136
  • 5
  • 7
1

Maybe this is a solution for variable sorting scheme. The sort attribute is just given, like ['Attributes', 'Height']. This uses the properties Attributes and Height.

It features a temporary storage for faster sorting.

function sort(a, by) {
    return a.map(function (el, i) {
        return {
            index: i,
            value: by.reduce(function (obj, property) { return obj[property]; }, el)
        };
    }).sort(function (a, b) {
        return a.value - b.value;
    }).map(function (el) {
        return a[el.index];
    });
}
var users = [{ 'Name': 'John', 'Attributes': { 'Age': 5, 'Height': 1.5, 'Clothes': { 'Shirts': 5, 'Pants': 8 } } }, { 'Name': 'Andrew', 'Attributes': { 'Age': 9, 'Height': 1.8, 'Clothes': { 'Shirts': 2, 'Pants': 5 } } }, { 'Name': 'Lucifer', 'Attributes': { 'Age': 11, 'Height': 1.3, 'Clothes': { 'Shirts': 9, 'Pants': 4 } } }];
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Height']), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Clothes', 'Shirts']), 0, 4) + '</pre>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You would need:

  1. a way to represent how to access the sort key given an object
  2. a function that, given a sort key representation and an object, queries the object and produces the key

The manner of representation can be arbitrarily selected, so let's say we decide to encode the access someObject.foo.bar as the string "foo.bar". Then the key producing function would be (adapted from my answer here):

function produceKey(target, path) {
    var parts = path.split('.');

    while(parts.length) {
        var branch = parts.shift();
        if (typeof target[branch] === 'undefined') {
            return undefined;
        }

        target = target[branch];
    }

    return target;
}

which you could then use as:

function produceKey(target, path) {
  var parts = path.split('.');

  while(parts.length) {
    var branch = parts.shift();
    if (typeof target[branch] === 'undefined') {
      return undefined;
    }

    target = target[branch];
  }

  return target;
}

var obj = { foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] };

$(function() {
  $("#trigger").click(function() {
    $(".result").text(JSON.stringify(produceKey(obj, $("input").val())));
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Targe object: 
<pre>
{ foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] } 
</pre>


Property path: <input name="path" />
<button id="trigger">Produce value</button>
<div class="result"></div>
Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
0

If I understand your question correctly, you want the user to chose which attribute to sort the array by. Looking at your fiddle I think that what you need is accessing an attribute specified by the user, fortunately it's possible to specify a variable inside the brackets. Something like:

var obj = {name: 'john'}
var attr = "name";
console.log(obj[attr]); // prints 'john'

Here's your modified fiddle: https://jsfiddle.net/9s5bnfh5/1/

Alessandro
  • 51
  • 5
0

The cleanest way of doing this is passing a callback function to your sort algorithm that is executed to retrieve the value to compare on from the object:

function cbSort(toSort, callback)
{
    return toSort.sort(function(a, b) 
    {
        return callback(a) - callback(b);
    }
}

// usage
var data = [ { x: 1, y: 2 }, {x: 3, y: 1}];
var sorted = cbSort(data, function(item) {
    return item.x; // make this as complex as you like
});
vzwick
  • 11,008
  • 5
  • 43
  • 63