133

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

理想情况下,我想在执行字符串化之前按字母顺序对对象的属性进行排序,或者作为字符串化操作的一部分。在许多地方都有用于操作数组对象的代码,并且更改这些代码以始终以明确的顺序创建属性是很困难的。

建议将是最受欢迎的!

一个精简的例子:

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

这两个 stringify 调用的输出不同,并显示在我的数据的差异中,但我的应用程序并不关心属性的顺序。对象以多种方式和位置构造。

4

24 回答 24

104

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

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

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

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

你可以这样做:

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

要以您可以自己调整的方式以编程方式执行此操作,您需要将对象属性名称推送到数组中,然后按字母顺序对数组进行排序并遍历该数组(将按正确的顺序)并从对象中选择每个值那个命令。“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
});

同样,这应该保证您按字母顺序遍历。

最后,以最简单的方式更进一步,该库将递归地允许您对传递给它的任何 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));

输出

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
于 2013-04-23T11:18:57.860 回答
48

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

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);
}
于 2018-12-03T11:54:02.730 回答
42

我认为,如果您可以控制 JSON 的生成(听起来像是),那么出于您的目的,这可能是一个很好的解决方案:json-stable-stringify

来自项目网站:

具有自定义排序的确定性 JSON.stringify() 从字符串化结果中获取确定性哈希

如果生成的 JSON 是确定性的,您应该能够轻松区分/合并它。

于 2014-01-21T19:21:04.280 回答
31

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

JSON.stringify(obj, Object.keys(obj).sort())
于 2016-11-17T04:09:19.247 回答
15

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

测试用例:

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

  });

不推荐使用的答案:

ES2016 中的简洁版本。归功于 @codename ,来自https://stackoverflow.com/a/29622653/94148

function orderedJsonStringify(o) {
  return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}
于 2016-03-05T06:33:39.403 回答
14

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;
于 2017-04-26T14:16:37.153 回答
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);
于 2015-02-17T16:00:10.390 回答
4

这与 Satpal Singh 的回答相同

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"}"
于 2013-04-23T13:32:36.627 回答
4

您可以在 EcmaScript 2015 中按属性名称对对象进行排序

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
于 2016-03-05T06:48:10.920 回答
3

您可以向对象添加自定义toJSON函数,用于自定义输出。在函数内部,以特定顺序将当前属性添加到新对象应在字符串化时保留该顺序。

看这里:

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

没有用于控制排序的内置方法,因为 JSON 数据旨在通过键访问。

这是一个带有一个小例子的jsfiddle:

http://jsfiddle.net/Eq2Yw/

尝试注释掉toJSON函数 - 属性的顺序是相反的。请注意,这可能是特定于浏览器的,即规范中不正式支持排序。它适用于当前版本的 Firefox,但如果您想要一个 100% 健壮的解决方案,您可能必须编写自己的字符串化函数。

编辑:

另请参阅有关 stringify 的非确定性输出的 SO question,尤其是 Daff 关于浏览器差异的详细信息:

如何确定性地验证 JSON 对象是否未被修改?

于 2013-04-23T11:25:28.167 回答
3

我从@Jason Parham 那里得到了答案并做了一些改进

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

这解决了数组被转换为对象的问题,它还允许您定义如何对数组进行排序。

例子:

var data = { content: [{id: 3}, {id: 1}, {id: 2}] };
sortObject(data, (i1, i2) => i1.id - i2.id)

输出:

{content:[{id:1},{id:2},{id:3}]}
于 2018-01-05T11:02:55.127 回答
2

出于某种原因,接受的答案对我来说不适用于嵌套对象。这导致我自己编写代码。当我写这篇文章时,已经是 2019 年末了,该语言中还有更多可用的选项。

更新:我相信大卫弗隆的回答比我之前的尝试更可取,我对此嗤之以鼻。我的依赖于对 Object.entries(...) 的支持,因此不支持 Internet Explorer。

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

--

保留此旧版本以供历史参考

我发现对象中所有键的简单、扁平数组将起作用。在几乎所有浏览器(不是 Edge 或 Internet Explorer,可以预见)和 Node 12+ 中,现在Array.prototype.flatMap(...)可用,因此有一个相当短的解决方案。(lodash 等价物也可以。)我只在 Safari、Chrome 和 Firefox 中进行了测试,但我看不出它为什么不能在支持 flatMap 和标准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);
}

有了这个,您可以在没有 3rd 方依赖项的情况下进行字符串化,甚至可以传入您自己的排序算法,该算法对键值条目对进行排序,因此您可以按键、有效负载或两者的组合进行排序。适用于嵌套对象、数组和普通旧数据类型的任何混合。

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

印刷:

{
  "a": 2,
  "b": 1,
  "c": {
    "x": 3,
    "y": [
      2048,
      1999,
      {
        "f": 5,
        "g": "help",
        "x": false
      }
    ],
    "z": 4
  }
}

如果您必须与较旧的 JavaScript 引擎兼容,您可以使用这些稍微冗长的版本来模拟 flatMap 行为。客户端必须至少支持 ES5,所以不支持 Internet Explorer 8 或更低版本。

这些将返回与上述相同的结果。

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);
}
于 2019-10-02T20:16:31.390 回答
2

我只是重写了一个提到的例子来使用它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);
于 2021-03-18T06:19:32.427 回答
1

适用于 lodash、嵌套对象、对象属性的任何值:

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)

它依赖于 Chrome 和 Node 的行为,即分配给对象的第一个键首先由JSON.stringify.

于 2017-08-07T15:02:43.367 回答
0

我做了一个函数来对对象进行排序,并使用回调 .. 它实际上创建了一个新对象

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

并用 object 调用它。

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

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

JSON.stringify( result )

打印{"value":"msio","os":"windows","name":"anu"}, 并使用 value 进行排序。

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

JSON.stringify( result )

哪个打印{"os":"windows","value":"msio","name":"anu"}

于 2013-04-23T11:50:26.497 回答
0

尝试:

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);
于 2013-04-23T11:21:31.690 回答
0

很惊讶没有人提到 lodash 的isEqual功能。

在两个值之间执行深度比较以确定它们是否相等。

注意:此方法支持比较数组、数组缓冲区、布尔值、日期对象、错误对象、映射、数字、对象对象、正则表达式、集合、字符串、符号和类型化数组。对象对象通过它们自己的而不是继承的、可枚举的属性进行比较。函数和 DOM 节点通过严格相等来比较,即 ===。

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

对于最初的问题 - 键的顺序不一致 - 这是一个很好的解决方案 - 当然,如果它发现冲突就会停止,而不是盲目地序列化整个对象。

为避免导入整个库,请执行以下操作:

import { isEqual } from "lodash-es";

奖励示例:您还可以通过此自定义运算符将其与 RxJS 一起使用

export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> => 
                                                pipe(distinctUntilChanged(isEqual));
于 2019-08-10T07:03:09.993 回答
0

毕竟,它需要一个数组来缓存嵌套对象中的所有键(否则它将忽略未缓存的键。)最旧的答案是完全错误的,因为第二个参数不关心点符号。所以,答案(使用 Set)就变成了。

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())
}
于 2020-02-22T05:59:31.670 回答
0

这是一种克隆方法...在转换为 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 非常新,但似乎可以很好地处理 package.json 文件。

于 2020-07-23T01:14:15.780 回答
0

扩展 AJP 的答案,以处理数组:

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;
}
于 2018-06-08T10:55:11.537 回答
0

如果列表中的对象没有相同的属性,则在字符串化之前生成一个组合的主对象:

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

于 2017-05-09T19:08:36.513 回答
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 );
}

// 示例如下。在每一层中,对于像 {} 这样的对象,在键排序中展平。对于数组、数字或字符串,像/使用 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}"

于 2017-08-17T08:20:54.570 回答
0

不要与 Chrome 调试器的对象监控相混淆。它显示对象中的排序键,即使实际上它没有排序。在对对象进行字符串化之前,您必须对其进行排序。

于 2021-01-03T01:57:43.253 回答
-4

Array.sort对你有帮助的方法。例如:

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});
于 2013-04-23T11:04:05.250 回答