sort object properties and JSON.stringify

JavascriptJson

Javascript Problem Overview


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 replaced, the properties on the objects are listed in different orders (their creation order?). When I do JSON.stringify() on the array and save it, a diff shows the properties getting listed in different orders, which is annoying when trying to merge the data further with diff and merging tools.

Ideally I would like to sort the properties of the objects in alphabetical order prior to performing the stringify, or as part of the stringify operation. There is code for manipulating the array objects in many places, and altering these to always create properties in an explicit order would be difficult.

Suggestions would be most welcome!

A condensed example:

obj = {}; obj.name="X"; obj.os="linux";
JSON.stringify(obj);
obj = {}; obj.os="linux"; obj.name="X";
JSON.stringify(obj);

The output of these two stringify calls are different, and showing up in a diff of my data, but my application doesn't care about the ordering of properties. The objects are constructed in many ways and places.

Javascript Solutions


Solution 1 - Javascript

The simpler, modern and currently browser supported approach is simply this:

JSON.stringify(sortMyObj, Object.keys(sortMyObj).sort());

However, this method does remove any nested objects that aren't referenced and does not apply to objects within arrays. You will want to flatten the sorting object as well if you want something like this output:

{"a":{"h":4,"z":3},"b":2,"c":1}

You can do that with this:

var flattenObject = function(ob) {
	var toReturn = {};
	
	for (var i in ob) {
		if (!ob.hasOwnProperty(i)) continue;
		
		if ((typeof ob[i]) == 'object') {
			var flatObject = flattenObject(ob[i]);
			for (var x in flatObject) {
				if (!flatObject.hasOwnProperty(x)) continue;
				
				toReturn[i + '.' + x] = flatObject[x];
			}
		} else {
			toReturn[i] = ob[i];
		}
	}
	return toReturn;
};
var myFlattenedObj = flattenObject(sortMyObj);
JSON.stringify(myFlattenedObj, Object.keys(myFlattenedObj).sort());

To do it programmatically with something you can tweak yourself, you need to push the object property names into an array, then sort the array alphabetically and iterate through that array (which will be in the right order) and select each value from the object in that order. "hasOwnProperty" is checked also so you definitely have only the object's own properties. Here's an example:

var obj = {"a":1,"b":2,"c":3};

function iterateObjectAlphabetically(obj, callback) {
    var arr = [],
        i;
    
    for (i in obj) {
        if (obj.hasOwnProperty(i)) {
            arr.push(i);
        }
    }

    arr.sort();
    
    for (i = 0; i < arr.length; i++) {
        var key = obj[arr[i]];
        //console.log( obj[arr[i]] ); //here is the sorted value
        //do what you want with the object property
        if (callback) {
            // callback returns arguments for value, key and original object
            callback(obj[arr[i]], arr[i], obj);
        }
    }
}

iterateObjectAlphabetically(obj, function(val, key, obj) {
    //do something here
});

Again, this should guarantee that you iterate through in alphabetical order.

Finally, taking it further for the simplest way, this library will recursively allow you to sort any JSON you pass into it: https://www.npmjs.com/package/json-stable-stringify

var stringify = require('json-stable-stringify');
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
console.log(stringify(obj));

Output

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}

Solution 2 - Javascript

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)
{
    const allKeys = new Set();
    JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
    return JSON.stringify(obj, Array.from(allKeys).sort(), space);
}

Or if you want to support older browsers:

function JSONstringifyOrder(obj, space)
{
    var allKeys = [];
    var seen = {};
    JSON.stringify(obj, function (key, value) {
        if (!(key in seen)) {
            allKeys.push(key);
            seen[key] = null;
        }
        return value;
    });
    allKeys.sort();
    return JSON.stringify(obj, allKeys, space);
}

Solution 3 - Javascript

I think that if you are in control of the JSON generation (and it sounds like you are), then for your purposes this might be a good solution: json-stable-stringify

From the project website:

> deterministic JSON.stringify() with custom sorting to get > deterministic hashes from stringified results

If the JSON produced is deterministic you should be able to easily diff/merge it.

Solution 4 - Javascript

You can pass a sorted array of the property names as the second argument of JSON.stringify():

JSON.stringify(obj, Object.keys(obj).sort())

Solution 5 - Javascript

Update 2018-7-24:

This version sorts nested objects and supports array as well:

function sortObjByKey(value) {
  return (typeof value === 'object') ?
    (Array.isArray(value) ?
      value.map(sortObjByKey) :
      Object.keys(value).sort().reduce(
        (o, key) => {
          const v = value[key];
          o[key] = sortObjByKey(v);
          return o;
        }, {})
    ) :
    value;
}


function orderedJsonStringify(obj) {
  return JSON.stringify(sortObjByKey(obj));
}

Test case:

  describe('orderedJsonStringify', () => {
    it('make properties in order', () => {
      const obj = {
        name: 'foo',
        arr: [
          { x: 1, y: 2 },
          { y: 4, x: 3 },
        ],
        value: { y: 2, x: 1, },
      };
      expect(orderedJsonStringify(obj))
        .to.equal('{"arr":[{"x":1,"y":2},{"x":3,"y":4}],"name":"foo","value":{"x":1,"y":2}}');
    });

    it('support array', () => {
      const obj = [        { x: 1, y: 2 },        { y: 4, x: 3 },      ];
      expect(orderedJsonStringify(obj))
        .to.equal('[{"x":1,"y":2},{"x":3,"y":4}]');
    });

  });

Deprecated answer:

A concise version in ES2016. Credit to @codename , from https://stackoverflow.com/a/29622653/94148

function orderedJsonStringify(o) {
  return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}

Solution 6 - Javascript

JSON.stringify() replacer function for having object keys sorted in output (supports deeply nested objects).

const replacer = (key, value) =>
value instanceof Object && !(value instanceof Array) ? 
    Object.keys(value)
    .sort()
    .reduce((sorted, key) => {
        sorted[key] = value[key];
        return sorted 
    }, {}) :
    value;

// Usage
// JSON.stringify({c: 1, a: { d: 0, c: 1, e: {a: 0, 1: 4}}}, replacer);

GitHub Gist page here.

Solution 7 - Javascript

This is same as Satpal Singh's answer

function stringifyJSON(obj){
    keys = [];
    if(obj){
        for(var key in obj){
            keys.push(key);
        }
    }
    keys.sort();
    var tObj = {};
    var key;
    for(var index in keys){
        key = keys[index];
        tObj[ key ] = obj[ key ];
    }
    return JSON.stringify(tObj);
}

obj1 = {}; obj1.os="linux"; obj1.name="X";
stringifyJSON(obj1); //returns "{"name":"X","os":"linux"}"

obj2 = {}; obj2.name="X"; obj2.os="linux";
stringifyJSON(obj2); //returns "{"name":"X","os":"linux"}"

Solution 8 - Javascript

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);

Solution 9 - Javascript

You can sort object by property name in EcmaScript 2015

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}

Solution 10 - Javascript

You can add a custom toJSON function to your object which you can use to customise the output. Inside the function, adding current properties to a new object in a specific order should preserve that order when stringified.

See here:

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify

There's no in-built method for controlling ordering because JSON data is meant to be accessed by keys.

Here's a jsfiddle with a small example:

http://jsfiddle.net/Eq2Yw/

Try commenting out the toJSON function - the order of the properties is reversed. Please be aware that this may be browser-specific, i.e. ordering is not officially supported in the specification. It works in the current version of Firefox, but if you want a 100% robust solution, you may have to write your own stringifier function.

Edit:

Also see this SO question regarding stringify's non-deterministic output, especially Daff's details about browser differences:

https://stackoverflow.com/questions/8931967/is-there-a-deterministic-equivalent-of-json-stringify

Solution 11 - Javascript

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}]}

Solution 12 - Javascript

I just rewrote one of mentioned examples to use it in stringify

const stringifySort = (key, value) => {
	if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
	return Object.keys(value).sort().reduce((obj, key) => (obj[key]=value[key], obj), {});
};

JSON.stringify({name:"X", os:"linux"}, stringifySort);

Solution 13 - Javascript

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);
}

Solution 14 - Javascript

Works with lodash, nested objects, any value of object attribute:

function sort(myObj) {
  var sortedObj = {};
  Object.keys(myObj).sort().forEach(key => {
    sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : myObj[key]
  })
  return sortedObj;
}
JSON.stringify(sort(yourObj), null, 2)

It relies on Chrome's and Node's behaviour that the first key assigned to an object is outputted first by JSON.stringify.

Solution 15 - Javascript

Try:

function obj(){
  this.name = '';
  this.os = '';
}

a = new obj();
a.name = 'X',
a.os = 'linux';
JSON.stringify(a);
b = new obj();
b.os = 'linux';
b.name = 'X',
JSON.stringify(b);

Solution 16 - Javascript

I made a function to sort object, and with callback .. which actually create a new object

function sortObj( obj , callback ) {

    var r = [] ;
    
    for ( var i in obj ){
        if ( obj.hasOwnProperty( i ) ) {
             r.push( { key: i , value : obj[i] } );
        }
    }
    
    return r.sort( callback ).reduce( function( obj , n ){
        obj[ n.key ] = n.value ;
        return obj;
    },{});
}

and call it with object .

var obj = {
    name : "anu",
    os : "windows",
    value : 'msio',
};

var result = sortObj( obj , function( a, b ){
    return a.key < b.key  ;    
});

JSON.stringify( result )

which prints {"value":"msio","os":"windows","name":"anu"} , and for sorting with value .

var result = sortObj( obj , function( a, b ){
    return a.value < b.value  ;    
});

JSON.stringify( result )

which prints {"os":"windows","value":"msio","name":"anu"}

Solution 17 - Javascript

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() );

Solution 18 - Javascript

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}"

Solution 19 - Javascript

Extending AJP's answer, to handle arrays:

function sort(myObj) {
    var sortedObj = {};
    Object.keys(myObj).sort().forEach(key => {
        sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : _.isArray(myObj[key])? myObj[key].map(sort) : myObj[key]
    })
    return sortedObj;
}

Solution 20 - Javascript

Surprised nobody has mentioned lodash's isEqual function.

> Performs a deep comparison between two values to determine if they are > equivalent. > > Note: This method supports comparing arrays, array buffers, booleans, > date objects, error objects, maps, numbers, Object objects, regexes, > sets, strings, symbols, and typed arrays. Object objects are compared > by their own, not inherited, enumerable properties. Functions and DOM > nodes are compared by strict equality, i.e. ===.

>https://lodash.com/docs/4.17.11#isEqual

With the original problem - keys being inconsistently ordered - it's a great solution - and of course it will just stop if it finds a conflict instead of blindly serializing the whole object.

To avoid importing the whole library you do this:

import { isEqual } from "lodash-es";

Bonus example: You can also use this with RxJS with this custom operator

export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> => 
                                                pipe(distinctUntilChanged(isEqual));

Solution 21 - Javascript

After all, it needs an Array that caches all keys in the nested object (otherwise it will omit the uncached keys.) The oldest answer is just plain wrong, because second argument doesn't care about dot-notation. So, the answer (using Set) becomes.

function stableStringify (obj) {
  const keys = new Set()
  const getAndSortKeys = (a) => {
    if (a) {
      if (typeof a === 'object' && a.toString() === '[object Object]') {
        Object.keys(a).map((k) => {
          keys.add(k)
          getAndSortKeys(a[k])
        })
      } else if (Array.isArray(a)) {
        a.map((el) => getAndSortKeys(el))
      }
    }
  }
  getAndSortKeys(obj)
  return JSON.stringify(obj, Array.from(keys).sort())
}

Solution 22 - Javascript

Here is a clone approach...clone the object before converting to json:

function sort(o: any): any {
    if (null === o) return o;
    if (undefined === o) return o;
    if (typeof o !== "object") return o;
    if (Array.isArray(o)) {
        return o.map((item) => sort(item));
    }
    const keys = Object.keys(o).sort();
    const result = <any>{};
    keys.forEach((k) => (result[k] = sort(o[k])));
    return result;
}

If is very new but seems to work on package.json files fine.

Solution 23 - Javascript

Don't be confused with the object monitoring of Chrome debugger. It shows sorted keys in object, even though actually it is not sorted. You have to sort the object before you stringify it.

Solution 24 - Javascript

There is Array.sort method which can be helpful for you. For example:

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionInnovineView Question on Stackoverflow
Solution 1 - JavascriptmarksyzmView Answer on Stackoverflow
Solution 2 - JavascriptJorView Answer on Stackoverflow
Solution 3 - JavascriptStijn de WittView Answer on Stackoverflow
Solution 4 - JavascriptChristian d'HeureuseView Answer on Stackoverflow
Solution 5 - JavascriptaleungView Answer on Stackoverflow
Solution 6 - JavascriptDavid FurlongView Answer on Stackoverflow
Solution 7 - JavascriptGiridhar C RView Answer on Stackoverflow
Solution 8 - JavascriptJason ParhamView Answer on Stackoverflow
Solution 9 - JavascriptMayki NaykiView Answer on Stackoverflow
Solution 10 - JavascriptDave R.View Answer on Stackoverflow
Solution 11 - JavascriptPeterView Answer on Stackoverflow
Solution 12 - Javascriptlexa-bView Answer on Stackoverflow
Solution 13 - JavascriptMiles ElamView Answer on Stackoverflow
Solution 14 - JavascriptAJPView Answer on Stackoverflow
Solution 15 - Javascript3y3View Answer on Stackoverflow
Solution 16 - JavascriptrabView Answer on Stackoverflow
Solution 17 - JavascriptNiels Gjeding OlsenView Answer on Stackoverflow
Solution 18 - JavascriptsaintthorView Answer on Stackoverflow
Solution 19 - JavascriptgblffView Answer on Stackoverflow
Solution 20 - JavascriptSimon_WeaverView Answer on Stackoverflow
Solution 21 - JavascriptPolvView Answer on Stackoverflow
Solution 22 - JavascriptCorey AlixView Answer on Stackoverflow
Solution 23 - Javascriptuser1276700View Answer on Stackoverflow
Solution 24 - JavascriptEgor4egView Answer on Stackoverflow