sort object properties and JSON.stringify

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.

29 thoughts on “sort object properties and JSON.stringify”

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

    yourBigArray.sort(function(a,b){
        //custom sorting mechanism
    });
    
    Reply
  2. 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}
    
    Reply
  3. 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:

    How to deterministically verify that a JSON object hasn't been modified?

    Reply
  4. 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);
    
    Reply
  5. 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"}

    Reply
  6. 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.

    Reply
  7. 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), {}));
    }
    
    Reply
  8. 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);
    
    Reply
  9. 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), {});
    }
    
    Reply
  10. You can pass a sorted array of the property names as the second argument of JSON.stringify():

    JSON.stringify(obj, Object.keys(obj).sort())
    
    Reply
  11. 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.

    Reply
  12. 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}”

    Reply
  13. 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}]}
    
    Reply
  14. 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() );
    Reply
  15. 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"}"
    
    Reply
  16. 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;
    }
    
    Reply
  17. 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));
    
    Reply
  18. 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 );
    }
    
    Reply
  19. 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())
    }
    
    Reply
  20. 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.

    Reply
  21. 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.

    Reply
  22. 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);
    }
    
    Reply

Leave a Comment