Sort Keys in Javascript Object

As of ECMAScript 2015 (ES6), an object’s own properties do have order for some operations, although relying on it is rarely a good idea. If you want order, usually it’s best to use an array or similar.

The order is:

  1. Let keys be a new empty List.
  2. For each own property key P of O that is an integer index, in ascending numeric index order
    • Add P as the last element of keys.
  3. For each own property key P of O that is a String but is not an integer index, in property creation order
    • Add P as the last element of keys.
  4. For each own property key P of O that is a Symbol, in property creation order
    • Add P as the last element of keys.
  5. Return keys.

That’s for “own” properties. I don’t think there are any externally-available operations that define a required order for all properties including inherited ones. (for-in is not required to follow the order above, not even in ES2015+.) As of ES2019, for-in does have a defined order (with some exceptions).

This means that it’s probably possible to do what you asked, on a compliant engine, provided none of our keys qualifies as as an integer index.

JSON still has no order, but JSON.stringify is required by the JavaScript spec to use the order described above.

I’m not saying I suggest it. 🙂

function sort(object) {
    // Don't try to sort things that aren't objects
    if (typeof object != "object") {
        return object;
    }

    // Don't sort arrays, but do sort their contents
    if (Array.isArray(object)) {
        object.forEach(function(entry, index) {
            object[index] = sort(entry);
        });
        return object;
    }

    // Sort the keys
    var keys = Object.keys(object);
    keys.sort(function (a, b) {
        var atype = typeof object[a],
            btype = typeof object[b],
            rv;
        if (atype !== btype && (atype === "object" || btype === "object")) {
            // Non-objects before objects
            rv = atype === 'object' ? 1 : -1;
        } else {
            // Alphabetical within categories
            rv = a.localeCompare(b);
        }
        return rv;
    });

    // Create new object in the new order, sorting
    // its subordinate properties as necessary
    var newObject = {};
    keys.forEach(function(key) {
        newObject[key] = sort(object[key]);
    });
    return newObject;
}

Live Example (I also updated the fiddle):

function sort(object) {
    // Don't try to sort things that aren't objects
    if (typeof object != "object") {
        return object;
    }
    
    // Don't sort arrays, but do sort their contents
    if (Array.isArray(object)) {
        object.forEach(function(entry, index) {
            object[index] = sort(entry);
        });
        return object;
    }
    
    // Sort the keys
    var keys = Object.keys(object);
    keys.sort(function (a, b) {
        var atype = typeof object[a],
            btype = typeof object[b],
            rv;
        if (atype !== btype && (atype === "object" || btype === "object")) {
            // Non-objects before objects
            rv = atype === 'object' ? 1 : -1;
        } else {
            // Alphabetical within categories
            rv = a.localeCompare(b);
        }
        return rv;
    });
    
    // Create new object in the new order, sorting
    // its subordinate properties as necessary
    var newObject = {};
    keys.forEach(function(key) {
        newObject[key] = sort(object[key]);
    });
    return newObject;
}

var object = {
    family: [{
        home: {
            city: 'Madrid'
        },
        birth: {
            city: 'Madrid'
        },
        name: 'John',
        age: 32

    }, {
        home: {
            city: 'London'
        },
        birth: {
            city: 'Paris'
        },
        name: 'Marie',
        age: 25
    }],
    name: 'Dani',
    age: 33
};
var sortedObject = sort(object);
document.getElementById('container').innerHTML = JSON.stringify(sortedObject, null, '\t');
<pre id="container">
</pre>

(You didn’t ask for alphabetical within categories, but it seemed a reasonable thing to throw in.)

That works for me on current Chrome, Firefox, and IE11.

Leave a Comment