简体   繁体   中英

Sorting array of JSON objects by properties with an order of importance

I have a bunch of songs stored in an array of JavaScript objects. It looks like this:

var library = [
{ "title": "40 Years Later",       "album": "Tears of Steel", "year": 2012, "track": 1, "disk": 1, "time": 31  },
{ "title": "The Dome",             "album": "Tears of Steel", "year": 2012, "track": 2, "disk": 1, "time": 311 },
{ "title": "The Battle",           "album": "Tears of Steel", "year": 2012, "track": 3, "disk": 1, "time": 123 },
{ "title": "End Credits",          "album": "Tears of Steel", "year": 2012, "track": 4, "disk": 1, "time": 103 },
{ "title": "The Wires",           "album": "Elephants Dream", "year": 2006, "track": 1, "disk": 1, "time": 75  },
{ "title": "Typewriter Dance",    "album": "Elephants Dream", "year": 2006, "track": 2, "disk": 1, "time": 70  },
{ "title": "The Safest Place",    "album": "Elephants Dream", "year": 2006, "track": 3, "disk": 1, "time": 45  },
{ "title": "Emo Creates",         "album": "Elephants Dream", "year": 2006, "track": 4, "disk": 1, "time": 60  },
{ "title": "End Title",           "album": "Elephants Dream", "year": 2006, "track": 5, "disk": 1, "time": 91  },
{ "title": "Teaser Music",        "album": "Elephants Dream", "year": 2006, "track": 6, "disk": 1, "time": 75  },
{ "title": "Ambience",            "album": "Elephants Dream", "year": 2006, "track": 7, "disk": 1, "time": 110 },
{ "title": "Snow Fight",                   "album": "Sintel", "year": 2010, "track": 1, "disk": 1, "time": 107 },
{ "title": "Finding Scales / Chicken Run", "album": "Sintel", "year": 2010, "track": 2, "disk": 1, "time": 107 },
{ "title": "The Ziggurat",                 "album": "Sintel", "year": 2010, "track": 3, "disk": 1, "time": 78  },
{ "title": "Expedition",                   "album": "Sintel", "year": 2010, "track": 4, "disk": 1, "time": 93  },
{ "title": "Dragon Blood Tree",            "album": "Sintel", "year": 2010, "track": 5, "disk": 1, "time": 47  },
{ "title": "Cave Fight / Lament",          "album": "Sintel", "year": 2010, "track": 6, "disk": 1, "time": 145 },
{ "title": "I Move On (Sintel's Song)",    "album": "Sintel", "year": 2010, "track": 7, "disk": 1, "time": 169 },
{ "title": "Circling Dragons",             "album": "Sintel", "year": 2010, "track": 8, "disk": 1, "time": 28  },
{ "title": "Trailer Music",                "album": "Sintel", "year": 2010, "track": 9, "disk": 1, "time": 44  }
];

I need to sort by properties in both alphabetical and numerical order. There are lots of articles and questions on StackOverflow that cover sorting by a single value, and some that seem to cover multiple values but they have no order or importance.

I need to sort each object (song) by several values where each value is of a lower importance rank. For example:

album name (alphabetical) > disk number (numerical) > track number (numerical) > title (alphabetical) > etc.

This means that albums are together where each album is in alphabetical order. In each album are songs sorted by the disk number, so all songs in disk 1 are at the top, then all songs in disk 2, etc. Inside each grouping of disk numbers are songs sorted by track number. If there are multiple songs with the same track number, or no track number exists, they will be sorted by song title in alphabetical order. More properties can be given if track title is also the same.

Capitalization should not matter, and it should sort like normal alphabetical sorting would (if it started with a number, that would go before letters, and special characters would have their place like in traditional sorting).

I have tried using this code (which has hard-coded sort values and lacks disk numbers) but it is only sorting by the inner-most property in the code, track number.

library.sort(function (a, b) {
    if (a.album === b.album) {
        if (a.track === b.track) {
            var x = a.title.toLowerCase();
            var y = b.title.toLowerCase();
            return x < y ? -1 : x > y ? 1 : 0;
        }
        var x = a.track;
        var y = b.track;
        return x < y ? -1 : x > y ? 1 : 0;
    }
    return a.album.toLowerCase() - b.album.toLowerCase();
});

I am looking for a function that will rearrange the library array so that it is sorted as described above given something along the lines of the following input:

sort(library, [ "album", "disk", "track", "title" ]);

Individual values should also be able to be sorted in a descending order, maybe looking something like -album or ["album", true] . Syntax is flexible.

You can sort array of ojects with Alasql JavaScript SQL library. It supports sorting with many fields in any order (like SQL).

For many sorts Alasql may be faster than other sort functions, because it compiles queries to JavaScript and save these compiled query function to cache.

var res = alasql('SELECT *, LCASE(album) AS lalbum, LCASE(title) as ltitle FROM ? \
     ORDER BY lalbum DESC, disk, ltrack, ltitle',[library]);

Here I created two additional fields (lalbum and ltitle) for sort text in case-insensitive way.

Try this example with you data at jsFiddle

You could use a function like this:

function priority(opt) {
    if (!(opt instanceof Array)) {
        opt = Array.prototype.slice.call(arguments);
    }
    return function (a, b) {
        for (var i = 0; i < opt.length; ++i) {
            var option = opt[i];
            if (typeof option === 'string') {
                option = [option, '+'];
            }
            if (option.length < 2) {
                option[1] = '+';
            }
            if (a[option[0]] !== b[option[0]]) {
                if (a[option[0]] === undefined) return 1;
                if (b[option[0]] === undefined) return -1;
                if (typeof a[option[0]] === 'string' || typeof b[option[0]] === 'string') {
                    return (option[1] === '+' ? String(a[option[0]]).toLowerCase() < String(b[option[0]]).toLowerCase() : String(a[option[0]]).toLowerCase() > String(b[option[0]]).toLowerCase()) ? -1 : 1;
                } else {
                    return (option[1] === '+' ? a[option[0]] < b[option[0]] : a[option[0]] > b[option[0]]) ? -1 : 1;
                }
            }
        }
        return 0;
    };
}

it works like this:

library.sort(priority(['album', 'disk', ['title', '-']])

this will sort library by album ascending, then disk ascending, then title descending

Formal usage:

opt: array containing:

  • string (sorts key 'string' in ascending order)

or

  • array
    • 0: string (sorts key 'string')
    • 1: '-' or '+' for ascending or descending order (defaults to ascending)

I didn't implement it the exact way you said, because it would make keys beginning with - impossible to sort in ascending order.

EDIT:

alternate version, works with '-album' syntax:

function priority(opt) {
    if (!(opt instanceof Array)) {
        opt = Array.prototype.slice.call(arguments);
    }
    return function (a, b) {
        for (var i = 0; i < opt.length; ++i) {
            var order = opt[i].substr(0, 1),
                key = opt[i].substr(1);
            if (order !== '-' && order !== '+') {
                key = opt[i];
                order = '+';
            }
            if (a[key] !== b[key]) {
                if (a[key] === undefined) return 1;
                if (b[key] === undefined) return -1;
                if (typeof a[key] === 'string' || typeof b[key] === 'string') {
                    return (order === '+' ? String(a[key]).toLowerCase() < String(b[key]).toLowerCase() : String(a[key]).toLowerCase() > String(b[key]).toLowerCase()) ? -1 : 1;
                } else {
                    return (order === '+' ? a[key] < b[key] : a[key] > b[key]) ? -1 : 1;
                }
            }
        }
        return 0;
    };
}

to be used like this:

library.sort(priority(['album', '-year', '+title']));
or
library.sort(priority('album', '-year', '+title'));

Here you go.. one approach to the problem:

// This is kinda hacky, but it works
// "a" means do an alphabetical compare
// "n" means do a numeric compare.
var sortKeys = ["album", "a", "disk", "n", "track", "n", "title", "n"];

function byKey(ao, bo) {
    var l = sortKeys.length;
    var i = 0;
    var sortResult;

    // Walk through the keys  
    while (i < l) {
        // Get the field name
        var field = sortKeys[i];
        // Get the compare type
        var sortType = sortKeys[i + 1];

        // Get the values and force to string values
        var a = "" + ao[field];
        var b = "" + bo[field];

        console.log([field, sortType]);
        // Advance by two because we consume two array elements
        i += 2;

        // Our alphabletical compare
        if (sortType === "a") {
            if (a.toLowerCase() < b.toLowerCase()) {
                return -1;
            }

            if (a.toLowerCase() > b.toLowerCase()) {
                return +1;
            }

            if (a.toLowerCase() === b.toLowerCase()) {
                // Ok, these fields match.  Restart the loop
                // So it will try the next sort criteria in.
                continue;
            }

            throw ("Should never actually get here.");
        }

        if (sortType === "n") {
            // Cheap numeric compare
            return +a - +b;
        }


    }

    // A total match across all fields
    return 0;

}

library.sort(byKey);

console.log(JSON.stringify(library, null, 2));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM