// I use lodash's isEqual() is cloneDeep().
// Testing provided below.
function deepSortObject(object) {
const deepSort = (object) => {
// Null or undefined objects return immediately.
if (object == null) {
return object;
}
// Handle arrays.
if (Array.isArray(object)) {
return (
_.cloneDeep(object)
// Recursively sort each item in the array.
.map((item) => deepSort(item))
// Sort array itself.
.sort((a, b) => {
let workingA = a;
let workingB = b;
// Object or Array, we need to look at its first value...
if (typeof a === "object") {
workingA = a[Object.keys(a)[0]];
}
if (typeof b === "object") {
workingB = b[Object.keys(b)[0]];
}
if (Array.isArray(a)) {
workingA = a[0];
}
if (Array.isArray(b)) {
workingB = b[0];
}
// If either a or b was an object/array, we deep sort...
if (workingA !== a || workingB !== b) {
const sortedOrder = deepSort([workingA, workingB]);
if (_.isEqual(sortedOrder[0], workingA)) {
return -1;
} else {
return 1;
}
}
// If both were scalars, sort the normal way!
return a < b ? -1 : a > b ? 1 : 0;
})
);
}
// Anything other than Objects or Arrays just send it back.
if (typeof object != "object") {
return object;
}
// Handle objects.
const keys = Object.keys(object);
keys.sort();
const newObject = {};
for (let i = 0; i < keys.length; ++i) {
newObject[keys[i]] = deepSort(object[keys[i]]);
}
return newObject;
};
return deepSort(object);
}
// TESTING
const unsortedInput = {
ObjectC: {
propertyG_C: [[8, 7, 6], [5, 4, 3], [], [2, 1, 0]], // Array of arrays
propertyF_C: [
// This should result in sorting like: [2]'s a:0, [1]'s a:1, [0]'s a.x:5
{
b: 2,
a: [
{ b: 1, a: 10 }, // Sort array y by property a...
{ y: 0, x: 5 }, // vs property x
// Hot testing tip: change x to -1 and propertyF_C will sort it to the top!
],
},
{ c: 1, b: [1, 2, 0], a: 1 },
{ c: 0, b: [1, 2, 0], a: 0 },
],
propertyE_C: {
b: 2,
a: 1,
},
200: false,
100: true,
propertyB_C: true,
propertyC_C: 1,
propertyD_C: [2, 0, 1],
propertyA_C: "Blah",
},
ObjectA: {
propertyE_A: {
b: 2,
a: 1,
},
200: false,
100: true,
propertyB_A: true,
propertyC_A: 1,
propertyD_A: [2, 0, 1],
propertyA_A: "Blah",
},
ObjectB: {
propertyE_B: {
b: 2,
a: 1,
},
200: false,
100: true,
propertyB_B: true,
propertyC_B: 1,
propertyD_B: [2, 0, 1],
propertyA_B: "Blah",
},
};
const sortedOutput = {
ObjectA: {
100: true,
200: false,
propertyA_A: "Blah",
propertyB_A: true,
propertyC_A: 1,
propertyD_A: [0, 1, 2],
propertyE_A: {
a: 1,
b: 2,
},
},
ObjectB: {
100: true,
200: false,
propertyA_B: "Blah",
propertyB_B: true,
propertyC_B: 1,
propertyD_B: [0, 1, 2],
propertyE_B: {
a: 1,
b: 2,
},
},
ObjectC: {
100: true,
200: false,
propertyA_C: "Blah",
propertyB_C: true,
propertyC_C: 1,
propertyD_C: [0, 1, 2],
propertyE_C: {
a: 1,
b: 2,
},
propertyF_C: [
{ a: 0, b: [0, 1, 2], c: 0 },
{ a: 1, b: [0, 1, 2], c: 1 },
{
a: [
{ x: 5, y: 0 },
{ a: 10, b: 1 },
],
b: 2,
},
],
propertyG_C: [[0, 1, 2], [3, 4, 5], [6, 7, 8], []],
},
};
// Some basic testing...
console.log("Before sort, are the JSON strings the same?", JSON.stringify(unsortedInput) === JSON.stringify(sortedOutput));
console.log("After sort, are the JSON stirngs the same?", JSON.stringify(deepSortObject(unsortedInput)) === JSON.stringify(sortedOutput));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>