My application has a large array of objects, which I stringify and save them to the disk. Unfortunately, when the objects in the array are manipulated, and sometimes replac
I don't understand why the complexity of the current best answers is needed, to get all the keys recursively. Unless perfect performance is needed, it seems to me that we can just call JSON.stringify()
twice, the first time to get all the keys, and the second time, to really do the job. That way, all the recursion complexity is handled by stringify
, and we know that it knows its stuff, and how to handle each object type :
function JSONstringifyOrder( obj, space )
{
var allKeys = [];
JSON.stringify( obj, function( key, value ){ allKeys.push( key ); return value; } )
allKeys.sort();
return JSON.stringify( obj, allKeys, space );
}
I took the answer from @Jason Parham and made some improvements
function sortObject(obj, arraySorter) {
if(typeof obj !== 'object')
return obj
if (Array.isArray(obj)) {
if (arraySorter) {
obj.sort(arraySorter);
}
for (var i = 0; i < obj.length; i++) {
obj[i] = sortObject(obj[i], arraySorter);
}
return obj;
}
var temp = {};
var keys = [];
for(var key in obj)
keys.push(key);
keys.sort();
for(var index in keys)
temp[keys[index]] = sortObject(obj[keys[index]], arraySorter);
return temp;
}
This fixes the issue of arrays being converted to objects, and it also allows you to define how to sort arrays.
Example:
var data = { content: [{id: 3}, {id: 1}, {id: 2}] };
sortObject(data, (i1, i2) => i1.id - i2.id)
output:
{content:[{id:1},{id:2},{id:3}]}
The accepted answer does not work for me for nested objects for some reason. This led me to code up my own. As it's late 2019 when I write this, there are a few more options available within the language.
Update: I believe David Furlong's answer is a preferable approach to my earlier attempt, and I have riffed off that. Mine relies on support for Object.entries(...), so no Internet Explorer support.
function normalize(sortingFunction) {
return function(key, value) {
if (typeof value === 'object' && !Array.isArray(value)) {
return Object
.entries(value)
.sort(sortingFunction || undefined)
.reduce((acc, entry) => {
acc[entry[0]] = entry[1];
return acc;
}, {});
}
return value;
}
}
JSON.stringify(obj, normalize(), 2);
--
KEEPING THIS OLDER VERSION FOR HISTORICAL REFERENCE
I found that a simple, flat array of all keys in the object will work. In almost all browsers (not Edge or Internet explorer, predictably) and Node 12+ there is a fairly short solution now that Array.prototype.flatMap(...) is available. (The lodash equivalent would work too.) I have only tested in Safari, Chrome, and Firefox, but I see no reason why it wouldn't work anywhere else that supports flatMap and standard JSON.stringify(...).
function flattenEntries([key, value]) {
return (typeof value !== 'object')
? [ [ key, value ] ]
: [ [ key, value ], ...Object.entries(value).flatMap(flattenEntries) ];
}
function sortedStringify(obj, sorter, indent = 2) {
const allEntries = Object.entries(obj).flatMap(flattenEntries);
const sorted = allEntries.sort(sorter || undefined).map(entry => entry[0]);
return JSON.stringify(obj, sorted, indent);
}
With this, you can stringify with no 3rd-party dependencies and even pass in your own sort algorithm that sorts on the key-value entry pairs, so you can sort by key, payload, or a combination of the two. Works for nested objects, arrays, and any mixture of plain old data types.
const obj = {
"c": {
"z": 4,
"x": 3,
"y": [
2048,
1999,
{
"x": false,
"g": "help",
"f": 5
}
]
},
"a": 2,
"b": 1
};
console.log(sortedStringify(obj, null, 2));
Prints:
{
"a": 2,
"b": 1,
"c": {
"x": 3,
"y": [
2048,
1999,
{
"f": 5,
"g": "help",
"x": false
}
],
"z": 4
}
}
If you must have compatibility with older JavaScript engines, you could use these slightly more verbose versions that emulate flatMap behavior. Client must support at least ES5, so no Internet Explorer 8 or below.
These will return the same result as above.
function flattenEntries([key, value]) {
if (typeof value !== 'object') {
return [ [ key, value ] ];
}
const nestedEntries = Object
.entries(value)
.map(flattenEntries)
.reduce((acc, arr) => acc.concat(arr), []);
nestedEntries.unshift([ key, value ]);
return nestedEntries;
}
function sortedStringify(obj, sorter, indent = 2) {
const sortedKeys = Object
.entries(obj)
.map(flattenEntries)
.reduce((acc, arr) => acc.concat(arr), [])
.sort(sorter || undefined)
.map(entry => entry[0]);
return JSON.stringify(obj, sortedKeys, indent);
}
If objects in the list does not have same properties, generate a combined master object before stringify:
let arr=[ <object1>, <object2>, ... ]
let o = {}
for ( let i = 0; i < arr.length; i++ ) {
Object.assign( o, arr[i] );
}
JSON.stringify( arr, Object.keys( o ).sort() );
function FlatternInSort( obj ) {
if( typeof obj === 'object' )
{
if( obj.constructor === Object )
{ //here use underscore.js
let PaireStr = _( obj ).chain().pairs().sortBy( p => p[0] ).map( p => p.map( FlatternInSort ).join( ':' )).value().join( ',' );
return '{' + PaireStr + '}';
}
return '[' + obj.map( FlatternInSort ).join( ',' ) + ']';
}
return JSON.stringify( obj );
}
// example as below. in each layer, for objects like {}, flattened in key sort. for arrays, numbers or strings, flattened like/with JSON.stringify.
FlatternInSort( { c:9, b: { y: 4, z: 2, e: 9 }, F:4, a:[{j:8, h:3},{a:3,b:7}] } )
"{"F":4,"a":[{"h":3,"j":8},{"a":3,"b":7}],"b":{"e":9,"y":4,"z":2},"c":9}"
A recursive and simplified answer:
function sortObject(obj) {
if(typeof obj !== 'object')
return obj
var temp = {};
var keys = [];
for(var key in obj)
keys.push(key);
keys.sort();
for(var index in keys)
temp[keys[index]] = sortObject(obj[keys[index]]);
return temp;
}
var str = JSON.stringify(sortObject(obj), undefined, 4);