对象属性和 JSON.stringify 进行排序

我的应用程序有一个大的对象数组,我对它们进行字符串化并将它们保存到磁盘中。不幸的是,当操作数组中的对象时,有时会替换它们,对象上的属性会以不同的顺序列出(它们的创建顺序?).当我对数组执行 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);

这两个 stringify 调用的输出是不同的,并且显示在我的数据中,但是我的应用程序不关心属性的顺序。这些对象在许多方面和地方被构造。

89955 次浏览

有一个 Array.sort方法可以帮助你,例如:

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

更简单、现代和目前支持浏览器的方法很简单:

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}

试试:

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

您可以添加一个自定义 toJSON函数到您的对象,您可以使用它来自定义输出。在函数内部,以特定的顺序向新对象添加当前属性应该在字符串化时保持该顺序。

看这里:

Https://developer.mozilla.org/en-us/docs/javascript/reference/global_objects/json/stringify

没有控制排序的内置方法,因为 JSON 数据是由键访问的。

下面是一个小例子:

Http://jsfiddle.net/eq2yw/

尝试注释掉 toJSON函数-属性的顺序是相反的。请注意,这可能是特定于浏览器的,也就是说,规范中并不正式支持订购。它可以在当前版本的 Firefox 中工作,但是如果您想要一个100% 健壮的解决方案,您可能需要编写自己的字符串函数。

编辑:

还可以看到关于 stringify 的非确定性输出的 SO 问题,特别是 Deff 关于浏览器差异的详细信息:

如何确定性地验证一个 JSON 对象没有被修改?

我创建了一个函数来对对象进行排序,然后用回调函数创建一个新的对象

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

并用对象调用它。

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"},并使用值进行排序。

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


JSON.stringify( result )

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

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

我认为,如果您控制了 JSON 生成(听起来似乎是这样) ,那么对于您的目的,这可能是一个很好的解决方案: Json stable Stringify

项目网站:

通过自定义排序获取确定性 JSON.stringify () 由严格化结果得到的确定性散列

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

一个递归简化的答案:

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

更新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中的简明版本。 信用从 https://stackoverflow.com/a/29622653/94148发送到@codename

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

可以在 EcmaScript2015中按属性名对对象进行排序

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

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

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

用于在输出中对对象键进行排序的 JSON.stringify()替换函数(支持深度嵌套对象)。

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 页面 给你

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

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

可用于 loash、嵌套对象和任何对象属性值:

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输出。

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}

我从“ 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}]}

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

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

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

或者,如果你想支持更老的浏览器:

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

令人惊讶的是没有人提到 loash 的 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年末了,所以在这种语言中还有一些其他的选项。

更新: 我相信 David Furlong 的回答是一个更好的方法,我早先的尝试,我已经草率了。Mine 依赖于对 Object.entry (...)的支持,所以没有 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 + 中都有一个相当简短的解决方案,因为现在可以使用 Prototype.latMap (...)。(对应的 loash 也可以。)我只在 Safari、 Chrome 和 Firefox 中测试过,但是我看不出为什么它不能在其他支持 latMap 和标准 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);
}

使用这种方法,您可以不依赖于第三方进行字符串排序,甚至传递您自己的排序算法,该算法根据键-值条目对进行排序,因此您可以按键、有效负载或两者的组合进行排序。适用于嵌套对象、数组和任何普通旧数据类型的混合。

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 引擎 兼容,那么您可以使用这些稍微冗长一些的版本来模拟 latMap 行为。客户端必须至少支持 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);
}

毕竟,它需要一个 Array 来缓存嵌套对象中的所有键(否则它将省略未缓存的键)最古老的答案是完全错误的,因为第二个参数并不关心点符号。因此,答案(使用 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())
}

下面是一个克隆方法... ... 在转换到 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 文件上正常工作。

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

我只是重写了上面提到的一个例子,以便在 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);

在我发现像 Fast-json-stable-stringify这样的库之前(我自己还没有在生产环境中测试过它) ,我是这样做的:

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