排序对象属性和JSON.stringify

时间:2013-04-23 10:57:24

标签: 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);

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

24 个答案:

答案 0 :(得分:50)

更简单,更现代,目前支持浏览器的方法就是:

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

JSON.stringify(sortMyObj, Object.keys(flattenObject(sortMyObj)).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}

答案 1 :(得分:29)

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

从项目网站:

  

确定性JSON.stringify(),带有自定义排序   来自字符串化结果的确定性哈希

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

答案 2 :(得分:20)

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

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

答案 3 :(得分:13)

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

答案 4 :(得分:6)

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

stringify

答案 5 :(得分: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"}"

答案 6 :(得分:3)

一个递归和简化的答案:

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

答案 7 :(得分:3)

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

见这里:

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

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

这里有一个小例子:

http://jsfiddle.net/Eq2Yw/

尝试注释toJSON函数 - 属性的顺序颠倒过来。请注意,这可能是特定于浏览器的,即规范中未正式支持订购。它适用于当前版本的Firefox,但是如果你想要一个100%强大的解决方案,你可能必须编写自己的字符串函数。

修改

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

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

答案 8 :(得分: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}]}

答案 9 :(得分:2)

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

#!/usr/bin/perl
use strict;
use warnings;
my @expressions = ("a=b","a>b","a<b","a!=b","a-b"); 
for my $exp (@expressions) { 
  $exp =~ s/^(.).*(.)$/$1$2/; 
  print "$1$2 is the same as $exp\n";
};

答案 10 :(得分:1)

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;

答案 11 :(得分: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输出。

答案 12 :(得分: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;
}

如果是非常新的东西,但似乎可以在package.json文件上正常工作。

答案 13 :(得分:0)

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

答案 14 :(得分:0)

毕竟,它需要一个可在嵌套对象中缓存所有键的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())
}

答案 15 :(得分:0)

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

更新:我相信David Furlong's answer是我较早尝试的一种更好的方法,并且我已经解决了。我的依赖于对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(...)可用了。 (等效的破折号也可以使用。)我仅在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);
}

有了这个,您就可以在没有第三方依赖的情况下进行字符串化,甚至可以传入您自己的对键-值条目对进行排序的排序算法,因此您可以按键,有效负载或二者的组合进行排序。适用于嵌套对象,数组以及纯旧数据类型的任何混合。

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

答案 16 :(得分: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));

答案 17 :(得分:0)

我刚刚重写了一个提到的示例,以便在 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);

答案 18 :(得分: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;
}

答案 19 :(得分: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”,一个 “:[{” H “:3,” J “:8},{” 一 “:3,” b “的:7}]中,” b“:{ “E”:9, “Y”:4, “z” 的:2}, “C”:9}“

  

答案 20 :(得分:0)

如果列表中的对象没有相同的属性,请在stringify之前生成组合的主对象:

&#13;
&#13;
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() );
&#13;
&#13;
&#13;

答案 21 :(得分: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;
    },{});
}

并用对象调用它。

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

答案 22 :(得分: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);

答案 23 :(得分:-3)

Array.sort方法对您有所帮助。例如:

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