排序对象属性和JSON.stringify

Inn*_*ine 76 javascript json

我的应用程序有一大堆对象,我将其字符串化并保存到磁盘.遗憾的是,当数组中的对象被操纵并且有时被替换时,对象上的属性以不同的顺序(它们的创建顺序?)列出.当我对数组执行JSON.stringify()并保存它时,diff会显示以不同顺序列出的属性,这在尝试使用diff和合并工具进一步合并数据时会很烦人.

理想情况下,我想在执行stringify之前按字母顺序对对象的属性进行排序,或者作为stringify操作的一部分.存在用于在许多地方操纵数组对象的代码,并且改变它们以总是以显式顺序创建属性将是困难的.

建议是最受欢迎的!

一个浓缩的例子:

obj = {}; obj.name="X"; obj.os="linux";
JSON.stringify(obj);
obj = {}; obj.os="linux"; obj.name="X";
JSON.stringify(obj);
Run Code Online (Sandbox Code Playgroud)

这两个stringify调用的输出是不同的,并显示在我的数据的差异中,但我的应用程序不关心属性的排序..对象是以多种方式构建的.

mar*_*yzm 62

更简单,现代和当前浏览器支持的方法就是这样:

JSON.stringify(sortMyObj, Object.keys(sortMyObj).sort());
Run Code Online (Sandbox Code Playgroud)

但是,此方法会删除未引用的任何嵌套对象,并且不会应用于数组中的对象.如果你想要这样的输出,你也想要展平排序对象:

{"a":{"h":4,"z":3},"b":2,"c":1}
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

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

JSON.stringify(sortMyObj, Object.keys(flattenObject(sortMyObj)).sort());
Run Code Online (Sandbox Code Playgroud)

要以编程方式使用您可以自行调整的内容,您需要将对象属性名称推送到数组中,然后按字母顺序对数组进行排序并遍历该数组(将按照正确的顺序)并从对象中选择每个值那个命令.还检查"hasOwnProperty",因此您肯定只拥有对象自己的属性.这是一个例子:

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
});
Run Code Online (Sandbox Code Playgroud)

同样,这应该保证您按字母顺序迭代.

最后,以最简单的方式进一步研究,这个库将以递归方式允许您对传递给它的任何JSON进行排序: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));
Run Code Online (Sandbox Code Playgroud)

产量

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
Run Code Online (Sandbox Code Playgroud)

  • 这是我试图避免的:)但是,谢谢,似乎是正确的,如果重的解决方案. (8认同)
  • @Johann通过在这里使用回调,他将`iterateObjectAlphabetically`变成了一个可重用的函数.如果没有回调,'有效载荷代码'必须在函数内部.我认为这是一个非常优雅的解决方案,可用于许多库和JS核心本身.例如[Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). (2认同)
  • 啊,我必须把这一点信息放在答案的下一句话中!等一下... (2认同)

Sti*_*itt 34

我认为如果你掌控JSON一代(听起来像你),那么为了你的目的,这可能是一个很好的解决方案:json-stable-stringify

从项目网站:

确定性JSON.stringify(),使用自定义排序从字符串化结果中获取确定性哈希值

如果生成的JSON是确定性的,您应该能够轻松地对其进行差异/合并.

  • @Tom 另请注意,该存储库每周 (!) 下载量达 500 万次。换句话说,它正在被积极使用。拥有“未解决”的开放问题和/或拉取请求并没有多大意义。只是作者不关心这些问题或没有时间等。并非每个库都需要每几个月更新一次。事实上,恕我直言,最好的库很少更新。当某事完成时,它就完成了。我并不是说这个项目没有真正的问题,但如果有,请指出这些。我没有参与这个库 BTW。 (4认同)
  • 请注意,该存储库状况不佳:3 年没有更新,并且存在未解决的问题和拉取请求。我认为最好寻找一些较新的答案。 (2认同)
  • 该库的最新版本于 2 天前发布,距离上次更改已有 6 年。 (2认同)

Chr*_*use 25

您可以将属性名称的排序数组作为第二个参数传递JSON.stringify():

JSON.stringify(obj, Object.keys(obj).sort())
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果obj中包含嵌套对象,则嵌套键也必须出现在第二个参数中; 否则,他们将被丢弃!反例:`> JSON.stringify({a:{c:1,b:2}},['a'])'{"a":{}}'`构建列表的一种(hacky)方式所有相关键的关键是使用JSON.stringify遍历对象:`function orderedStringify(obj){const allKeys = []; JSON.stringify(obj,(k,v)=> {allKeys.push(k); return v;}); return JSON.stringify(obj,allKeys.sort()); 例子:`> orderedStringify({a:{c:1,b:2}})'{"a":{"b":2,"c":1}}'` (11认同)
  • Object.keys() *不是*递归的。这不适用于任何包含嵌套数组或对象的对象。 (4认同)
  • @richremer是的,在[ECMAScript标准](http://www.ecma-international.org/ecma-262/6.0)中,JSON.stringify()的第二个参数称为`replacer`,并且可以是数组。它用于构建一个“ PropertyList”,然后在“ SerializeJSONObject()”中使用该属性以该顺序附加属性。自ECMAScript 5起已对此进行了记录。 (3认同)
  • 对于这种行为有任何书面保证吗? (2认同)

ale*_*ung 14

更新2018-7-24:

此版本对嵌套对象进行排序并支持数组:

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));
}
Run Code Online (Sandbox Code Playgroud)

测试用例:

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

  });
Run Code Online (Sandbox Code Playgroud)

弃用答案:

ES2016中的简洁版本.感谢@codename,来自/sf/answers/2073585741/

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

  • 请注意,这与 Christian 的回答一样,对嵌套对象有不当行为。 (3认同)
  • 这个“sortObjByKey()”似乎没有检查循环引用,所以要小心,它可能会根据输入数据挂在无限循环中。示例:`var objA = {}; var objB = {objA: objA}; objA.objB = objB;` -&gt; `sortObjByKey(objA);` -&gt; `VM59:6 未捕获 RangeError: 超出最大调用堆栈大小` (2认同)

Jor*_*Jor 13

我不明白为什么要递归地获取所有密钥,为什么需要当前最佳答案的复杂性。除非需要完美的性能,在我看来,我们只能打JSON.stringify()两次电话,第一次是获取所有密钥,第二次是真正完成这项工作。这样,所有递归复杂性都由处理stringify,我们知道它知道它的内容,以及如何处理每种对象类型?

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 );
}
Run Code Online (Sandbox Code Playgroud)

  • 不,关键是`Object.keys()`不是递归的,所以如果您有一个像`{a:“ A”,b:{c:“ C”}}}之类的对象,并且在它将只获得键“ a”和“ b”,而不是键“ c”。JSON.stringify`知道关于JSON序列化过程处理的每个对象类型的递归的所有信息,无论是像对象的对象还是数组(并且它已经实现了识别两者的逻辑),因此它应该为我们正确处理所有内容。 (2认同)
  • 聪明的!re perf:我相信这是 O(N^2),N=对象中的数字键,因为针对对象中的每个键线性检查排序的键。因此,在 GHz 机器上,性能将开始成为 N &gt; 100K 左右的明显问题。对于 N &gt; 100 万机器来说,性能可能会减弱。参考:https://tc39.es/ecma262/#sec-json.stringify (2认同)

Dav*_*ong 11

https://gist.github.com/davidfurlong/463a83a33b70a3b6618e97ec9679e490

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

  • 这可能是最好的答案,谢谢@David (2认同)