169

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.

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
Innovine
  • 1,872
  • 2
  • 12
  • 10
  • Please give an example of the object you're trying to stringify (either JSON output or JS object literal) – Bojangles Apr 23 '13 at 10:59
  • 4
    Object keys in objects are not guaranteed to have a fixed order. This is by design. – Passerby Apr 23 '13 at 11:07
  • The only cross browser way I can think of is to modify a JSON.stringify implementation. – Dogbert Apr 23 '13 at 11:10
  • if ordering is important use array, keys will appears as they defined . may its order is not guaranteed . – rab Apr 23 '13 at 11:20
  • 1
    http://stackoverflow.com/questions/1359761/sorting-a-javascript-object might help you – Satpal Apr 23 '13 at 11:21
  • This code runs in nodejs on linux, so browser compatibility is not an issue. – Innovine Apr 23 '13 at 11:22
  • The ordering is not at all important to my application, apart from this one issue. My app can read the saved data back in just fine, it is the diff with the previous data only which is causing trouble. – Innovine Apr 23 '13 at 11:24
  • @Innovine, v8 also doesn't keep the order intact - http://code.google.com/p/v8/issues/detail?id=164 – Dogbert Apr 23 '13 at 11:28
  • @Dogbert it seems chrome fixed this issue ! – rab Apr 23 '13 at 12:14
  • 1
    @rab, where did you find that out? Note: It could work (and probably does most of the time), but I meant that it isn't guaranteed to. – Dogbert Apr 23 '13 at 12:23
  • **For who is using _Node.js_,** obtaining _normalized_ `JSON.stringify` results may be done with [`json-stable-stringify`](https://www.npmjs.com/package/json-stable-stringify) or [`json-normalize`](https://www.npmjs.com/package/json-normalize). – Константин Ван Feb 12 '18 at 20:56
  • **For who is trying to do this,** note that depending on an object key's order **_should_ be avoided in Javascript**, since it is **by design** that Javascript not guaranteeing the order. Please reconsider it and re-design the way in which your program works. – Константин Ван Feb 12 '18 at 20:58
  • 1
    OP here. There was no dependency on property order here, just a question on how to avoid diffs in the serialized data. This was eventually solved by stashing the properties in arrays and sorting them prior to serialization, much like in the accepted answer below. – Innovine Feb 13 '18 at 12:28
  • pity there isn't something like python's OrderedDict in JS.. any similar npm modules? – dcsan Feb 10 '23 at 05:39

26 Answers26

138

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}
francis duvivier
  • 2,056
  • 11
  • 17
marksyzm
  • 5,281
  • 2
  • 29
  • 27
  • 12
    This is very much what I was trying to avoid :) But thanks, seems like the correct, if heavy, solution. – Innovine Apr 23 '13 at 11:31
  • I agree - but this is really a situation where TIMTOWTDI doesn't apply, unfortunately. – marksyzm Apr 23 '13 at 12:24
  • 1
    There's a bug in this code. You can't reference `obj[i]` in the for loop because `i` is an integer, and the property names are not necessarily (hence this question). It should be `obj[arr[i]]`. – Andrew Ensley Jun 19 '13 at 21:40
  • No callback needed here. It's not asynchronous. – Johann Apr 21 '15 at 23:36
  • 1
    Callbacks aren't exclusive to asynchronous behaviour. – marksyzm Apr 23 '15 at 19:26
  • 2
    @Johann By using a callback here, he turns `iterateObjectAlphabetically` into a reusable function. Without the callback the 'payload code' would have to be inside the function itself. I think it's a very elegant solution and one that is used in many libraries and JS core itself. E.g. [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). – Stijn de Witt Sep 16 '15 at 08:44
  • I will simplify this with `Object.keys()` once browsers like IE8 drop off the market share count. – marksyzm Sep 16 '15 at 09:47
  • There is an answer that did the job for me here : https://stackoverflow.com/a/16543302/1579667 which says to use the second parameter of `JSON.stringify()` with an array of key names in the order you want them. – Benj Aug 25 '17 at 14:43
  • @Benj see first sentence – marksyzm Sep 06 '17 at 10:32
  • 1
    @marksyzm It's okay, in my case I had to stringify only a selection of fields. Thus, your answer put me on the way. Thanks – Benj Sep 08 '17 at 09:13
  • 1
    The one-liner does not work when there are objects in arrays, e.g., when `sortMyObj` is `{ z: [{ x: "y" }] }` – Joe Chung Sep 11 '18 at 07:59
  • @JoeChung I updated the description to include your point made – marksyzm Sep 12 '18 at 13:10
  • @JoeChung The one liner also doesn't apply to any depth of keys below the shallow point, nor does it apply to arrays within an object. It only sorts the shallow point of the keys provided as that is the expected behaviour of `Object.keys()`. It was a common sense expectation that the developer would understand this from looking at the code they are looking at. – marksyzm Sep 12 '18 at 13:14
  • 1
    The second form (stringify after flattening) has a small bug: the first argument to `JSON.stringify` should be `flattenObj(sortMyObj)` not `sortMyObj`. Have put an edit to that effect. – iPherian Nov 02 '19 at 01:44
  • I have rejected the change you submitted as the function originally delivers the same result regardless and it is unnecessary to run the one I've put in twice as it filters the properties of the original object. Mine is both more efficient, easier to read and makes proper use of `JSON.stringify`'s arguments. – marksyzm Nov 04 '19 at 09:48
  • This *simpler, modern and currently browser supported approach* completely fails on nested objects. – Matej Kormuth Jul 29 '20 at 17:26
  • 2
    Ah, I must put that bit of information in the very next sentence in the answer! Oh wait... – marksyzm Jul 30 '20 at 18:23
64

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);
}
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
Jor
  • 860
  • 7
  • 8
  • thats interesting, but you've actually "cheated" using stringify to map all keys to an array. That array can be easier, better and safer obtained via Object.keys – Bernardo Dal Corno Dec 13 '18 at 16:57
  • 11
    No, the point is that `Object.keys()` is not recursive, so if you have an object like `{a: "A", b: {c: "C"} }` and you call Object.keys on it, you will only get keys `a` and `b`, but not `c`. `JSON.stringify` knows everything about recursion on every object type handled by the JSON serialization process, be it an object-like or an array (and it already implements the logic to recognize both), so it should handle everything correctly for us. – Jor Dec 13 '18 at 21:06
  • 1
    Love this solution, very elegant and fail safe it seems. – Markus Mar 26 '19 at 23:04
  • @Jor thanks, that works for me. Others snippets I tried failed one way or another. – nevf Oct 29 '20 at 23:40
  • 3
    clever! re perf: I believe this is O(N^2), N=number keys in object, as the sorted keys are checked linearly for every key in the object. So perf will start to be noticeable issue around N > 100K on GHz machines.and perhaps debilitating for N > 1Million. ref: https://tc39.es/ecma262/#sec-json.stringify – Mike Liddell Nov 21 '20 at 00:17
  • works!11 it seems like its based of a feature of replacer as an array that sorts the values using the order of the array. its not very well documented in the Mozilla docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify – yigal Mar 24 '21 at 02:35
  • @yigal It seems this behavior is the one described for JSON.stringify() in the EcmaScript specification here, so it should be reliable : https://262.ecma-international.org/11.0/#sec-json.stringify – Jor Apr 02 '21 at 12:18
  • this is very nice, but i would leave out the last `stringify` call and instead just have this be a function returning the array of keys to be passed to a (subsequent) call to `stringify`. – John Glassmyer Jan 17 '22 at 03:34
  • Note: this will sort the keys in the outermost layer of the object. But it's possible that the object has other objects nested inside of it. In that case, you would want to sort those inner objects as well. – Kevin Jul 19 '23 at 18:53
46

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.

Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • 2
    Note that that repo is in bad shape: hasn't been updated in 3 years and has unresolved issues and pull requests. I think it is better to look to some of the newer answers. – Tom Feb 02 '19 at 15:29
  • 7
    @Tom Also note that that repo has 5 million weekly (!) downloads. In other words, it's actively being used. Having 'unresolved' open issues and/or pull requests does not mean much. Just that the author(s) don't care about those issues or have no time etc. Not every lib needs to be updated every couple of months. In fact, imho, the best libs get updates very infrequently. When something is done, it's done. I'm not saying this project has no real issues, but if so, please point those out. I have no involvement with this lib BTW. – Stijn de Witt Feb 18 '19 at 11:16
  • ES6 I guess broke it being able to sort objects (non-arrays), as specified. I need this for user editing. Even if order does not matter to the computer. It does to the users editing it. It was worth trying. – TamusJRoyce Jul 26 '19 at 20:37
  • 1
    On top of what @Tom says, I'll also point out that this project has no runtime dependencies. Meaning that if there are no security / maintenance issues in this repo, then it's probably pretty stable and safe to use. Also, I just tested it with ES6 and it seems to work fine (in Firefox at least). – Phil Feb 26 '20 at 12:03
  • 1
    The latest version of the library was released 2 days ago, 6 years after the last change. – M. Justin Nov 09 '22 at 22:15
35

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.

Skulaurun Mrusal
  • 2,792
  • 1
  • 15
  • 29
David Furlong
  • 1,343
  • 1
  • 12
  • 15
  • 4
    this is probably the best answer out there, Thanks @David – vhugo Mar 18 '20 at 18:27
  • 1
    Great! Might expand the check: `value instanceof Object && !(value instanceof Array) && !(value instanceof Date) && !(value instanceof Function)` – BlueD Jan 21 '21 at 17:03
  • This is great. I think it depends on a few things I didn't realize. 1) Stringifying an object visits properties ["using the same algorithm as Object.keys(), which has a **well-defined order**"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 2) Object.keys() uses the "same order that a **normal loop** would"... (`for...in`) 3) `for...in` loops string keys ["...in ascending chronological **order of property creation.**"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) – Nate Anderson Sep 08 '22 at 18:31
  • @TheRedPea - Isn't the .sort() all about NOT relying on any order from Object.keys() ??? – spechter Oct 19 '22 at 11:25
  • @sphecter yes, but, if I understand the code in this answer, its `replacer` function takes an Object and [*returns a **new** Object*. "If you return any other object, the object is recursively stringified"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter) So it's still up to the JSON.stringify function to serialize the **new** Object! How do we know the JSON.stringify function will serialize the Object in a predictable order? MDN documentation explains: "Properties are visited using the same algorithm as Object.keys()" – Nate Anderson Oct 19 '22 at 17:00
  • In other words, the new object was assigned properties in predictable order, (thanks to `sort()`, like you said.) *and also* since `JSON.stringify` uses `Object.keys()` (according to MDN; internal to the implementation of `JSON.stringify`), *and also* since `Object.keys()` returns properties *in the order properties were assigned*, all these things work together to give us the resulting string with properties in alphabetical order. In other other words, this answer shows how to assign properties in alpha order, but still relies on JSON.stringify to serialize them in the order assigned! – Nate Anderson Oct 19 '22 at 17:04
  • Note that this will not quite yield alphabetical order, because numerical keys are always iterated in ascending before any string keys, no matter the insertion order. – riv Aug 14 '23 at 21:47
33

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

JSON.stringify(obj, Object.keys(obj).sort())
Christian d'Heureuse
  • 5,090
  • 1
  • 32
  • 28
  • 2
    Is there any sort of documented guarantee on this behavior? – rich remer Jan 21 '17 at 03:23
  • 4
    @richremer Yes, in the [ECMAScript standard](http://www.ecma-international.org/ecma-262/6.0), the second parameter of `JSON.stringify()` is called `replacer` and can be an array. It is used to build a `PropertyList` which is then used in `SerializeJSONObject()` to append the properties in that order. This is documented since ECMAScript 5. – Christian d'Heureuse Jan 21 '17 at 04:35
  • 23
    Note that if obj has nested objects in it, the nested keys must be present in the second argument as well; otherwise, they will be dropped! Counter-example: `> JSON.stringify({a: {c: 1, b: 2}}, ['a']) '{"a":{}}'` One (hacky) way to build the list of all relevant keys is to use JSON.stringify to traverse the object: `function orderedStringify(obj) { const allKeys = []; JSON.stringify(obj, (k, v) => { allKeys.push(k); return v; }); return JSON.stringify(obj, allKeys.sort()); }` Example: `> orderedStringify({a: {c: 1, b: 2}}) '{"a":{"b":2,"c":1}}'` – Saif Hakim Apr 05 '17 at 17:24
  • 4
    Object.keys() is *not* recursive. This will not work for any object containing nested arrays or objects. – Jor Feb 29 '20 at 13:57
18

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), {}));
}
aleung
  • 9,848
  • 3
  • 55
  • 69
  • Syntax not quite right. Should be - `function orderedJsonStringify(o) { return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {})); }` – Ben May 27 '16 at 09:58
  • This, for now, has fixed my issue with C# DataContractJsonSerializer and "__type" not being listed first in the json string. Thanks. – Yogurt The Wise Sep 02 '16 at 21:18
  • 3
    Note that this, like Christian's answer, misbehaves with nested objects. – Daniel Griscom Nov 20 '17 at 21:10
  • sortObjPropertiesByKey is not defined. Did you mean to use sortObjByKey? – Paul Lynch Sep 05 '18 at 21:14
  • 2
    It does not seem that this `sortObjByKey()` checks for circular references, so be careful, it may hang in an infinite loop depending on input data. Example: `var objA = {}; var objB = {objA: objA}; objA.objB = objB;` -> `sortObjByKey(objA);` -> `VM59:6 Uncaught RangeError: Maximum call stack size exceeded` – Klesun Dec 08 '19 at 21:50
4

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"}"
Giridhar C R
  • 180
  • 1
  • 8
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);
Jason Parham
  • 466
  • 6
  • 10
  • `reorder` should be renamed to `sortObject` and this doesn't handle arrays – jgawrych Apr 02 '15 at 00:42
  • Thanks for noticing that, and OP's issue was resorting objects, not arrays. – Jason Parham Apr 02 '15 at 13:23
  • @JonathanGawrych Why would you sort arrays anyway, they are supposed to have an order. An array [1,2,3] is not the same as an array [2,3,1], but an object has no property order. The problem is that order suddenly matters after stringification - the strings for 100% equivalent objects can be different. Unfortunately the effort to get a deterministic stringify seems considerable, example package: https://github.com/substack/json-stable-stringify/blob/master/index.js – Mörre Nov 25 '16 at 14:46
  • 1
    @Mörre - I guess I wasn't specific enough. By "doesn't handle arrays," I meant arrays are broken, not left unsorted. The problem is because `typeof arr === "object"` it would transform arrays into objects. For example `var obj = {foo: ["bar", "baz"]}` would be stringified into `{ "foo": { "0": "bar", "1": "baz"} }` – jgawrych Nov 29 '16 at 19:12
  • to fix the issue found by @JonathanGawrych: `if(obj.constructor.prototype !== Object.prototype)` – ricka Nov 09 '17 at 06:29
  • @ricka correct me if I'm wrong but wouldn't that leave objects inside arrays unsorted? – Peter Jan 05 '18 at 10:39
  • I have made some improvements and posted my own answer https://stackoverflow.com/a/48112249/58553 – Peter Jan 05 '18 at 11:03
4

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), {});
}
Mike
  • 737
  • 1
  • 7
  • 11
  • Maybe a better name would be `sortObjectPropertiesByName()` or more simple `sortPropertiesByName()`. – Jan Nov 13 '18 at 13:03
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?

Community
  • 1
  • 1
Dave R.
  • 7,206
  • 3
  • 30
  • 52
  • The properties of each object are known (and mostly strings) so hardcoding the properties in my own toJSON stringifier might be much quicker than sorting...! – Innovine Apr 23 '13 at 11:37
  • The 'orderedJsonStringify' below almost worked. But this looks to be a better solution for me. When i need to get '__type' of the C# DataContractJsonSerializer to be the first item in the json string. var json = JSON.stringify(myjsonobj, ['__type', 'id', 'text', 'somesubobj' etc.....]); a little pain to have list all the keys. – Yogurt The Wise Sep 09 '16 at 19:12
3

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}]}
Peter
  • 37,042
  • 39
  • 142
  • 198
3

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);
lexa-b
  • 1,759
  • 15
  • 15
3

An additional solution that works for nested objects as well:

const myFunc = (key) =>
  JSON.stringify(key, (_, v) =>
    v.constructor === Object ? Object.entries(v).sort() : v
  );
  
const jsonFunc = JSON.stringify;
  
const obj1 = {
  key1: "value1",
  key2: {
    key3: "value2",
    key4: "value3",
  },
};
  
const obj2 = {
  key2: {
    key4: "value3",
    key3: "value2",
  },
  key1: "value1",
};

console.log(`JSON: ${jsonFunc(obj1) === jsonFunc(obj2)}`);
console.log(`My: ${myFunc(obj1) === myFunc(obj2)}`);
jonirap
  • 31
  • 1
2

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);
}
Miles Elam
  • 1,440
  • 11
  • 19
1

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.

AJP
  • 26,547
  • 23
  • 88
  • 127
  • 2
    Thanks, but I had this problem 4 years ago, I'm happy to say I've moved on a bit since then :) – Innovine Aug 08 '17 at 19:20
  • 1
    :) Good to hear it. Though I had this problem yesterday and didn't find the answer I was looking for under your (old but still relevant) question :) – AJP Aug 09 '17 at 13:00
1

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())
}
Polv
  • 1,918
  • 1
  • 20
  • 31
0

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);
3y3
  • 802
  • 1
  • 6
  • 18
  • Thanks, but apparently the ordering is undefined and only happens to work. Interesting approach though! – Innovine Apr 23 '13 at 11:28
0

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

rab
  • 4,134
  • 1
  • 29
  • 42
0

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() );
0
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}"

saintthor
  • 41
  • 7
  • Please explain why using an external lib, and as @DeKaNszn said, add some explanations and an introduction. It would be appreciated. – Benj Aug 25 '17 at 14:32
  • this funtion is copied from my project. in which i use underscore.js. – saintthor Aug 26 '17 at 15:25
0

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;
}
gblff
  • 1
0

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));
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
0

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.

Corey Alix
  • 2,694
  • 2
  • 27
  • 38
0

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.

  • Thanks for pointing this out. The above was for a nodejs application, but if Chrome's debugger is sorting things by itself then that's certainly useful information for someone, someday. Maybe they will comment here when they see this :) – Innovine Jan 03 '21 at 15:40
0

Before I found libs like fast-json-stable-stringify (haven't tested it in production myself), I was doing it this way:

import { flatten } from "flat";
import { set } from 'lodash/fp';

const sortJson = (jsonString) => {
  const object = JSON.parse(jsonString);
  const flatObject = flatten(object);
  const propsSorted = Object.entries(flatObject).map(([key, value]) => ({ key, value })).sort((a, b) => a.key.localeCompare(b.key));
  const objectSorted = propsSorted.reduce((object, { key, value }) => set(key, value, object), {});
  return JSON.stringify(objectSorted);
};


const originalJson = JSON.stringify({ c: { z: 3, x: 1, y: 2 }, a: true, b: [ 'a', 'b', 'c' ] });
console.log(sortJson(originalJson)); // {"a":true,"b":["a","b","c"],"c":{"x":1,"y":2,"z":3}} 
Fappaz
  • 3,033
  • 3
  • 28
  • 39
-4

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

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});
sebix
  • 2,943
  • 2
  • 28
  • 43
Egor4eg
  • 2,678
  • 1
  • 22
  • 41
  • 1
    I don't wish to sort the array though. Actually the array part is not important.. I wish to sort the properties of an object.. from a few quick experiments it looks like properties are listed in the order that they are created. One way might be to create a new object and copy the properties in alphabetical order but I'm hoping there is something easier/quicker.. – Innovine Apr 23 '13 at 11:07
  • 1
    @Innovine, even that wouldn't work as keys are not guaranteed to be ordered by creation time by the spec. – Dogbert Apr 23 '13 at 11:09
  • Then the question becomes, how do I stringify my objects in such a way that the keys are in a fixed order.. it's not impossible for me to do for (key in obj) and store them in a temp array and sort that and manually construct a string.. but I'm desperately hoping there is an easier way – Innovine Apr 23 '13 at 11:21
  • No, that is pretty much the way to do it. Welcome to JavaScript. – marksyzm Apr 23 '13 at 11:26