2013-04-23 238 views
43

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

理想情况下,我想按照字母顺序排列对象的属性,然后再执行stringify,或者作为stringify操作的一部分。在很多地方都有用于处理数组对象的代码,并且改变这些以始终以明确的顺序创建属性将很困难。

建议将非常欢迎!

的稠例如:

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

这两个字符串化调用的输出是不同的,并显示在我的数据的差异,但我的应用程序不关心性能..的的排序对象在许多方面和地方构建..

+0

请给你试图字符串化(或者JSON输出或JS对象常量)中的对象 – Bojangles 2013-04-23 10:59:27

+4

对象键不是对象的实例保证有一个固定的订单。这是设计。 – Passerby 2013-04-23 11:07:09

+0

我能想到的唯一跨浏览器方式是修改JSON.stringify实现。 – Dogbert 2013-04-23 11:10:19

回答

24

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

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

这是我试图避免的东西:)但是,谢谢,似乎是正确的,如果重的解决方案。 – Innovine 2013-04-23 11:31:11

+0

我同意 - 但这实际上是TIMTOWTDI不适用的情况,不幸的是。 – marksyzm 2013-04-23 12:24:34

+1

此代码中存在一个错误。你不能在for循环中引用'obj [i]',因为'i'是一个整数,属性名不一定是(因此这个问题)。它应该是'obj [arr [i]]'。 – 2013-06-19 21:40:15

-2

Array.sort方法,可以帮助你。例如:

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

我不想排序数组。实际上数组的一部分并不重要..我想排序一个对象的属性..从几个快速实验看来,属性按照它们创建的顺序列出。一种方式可能是创建一个新对象并按字母顺序复制属性,但我希望有更容易/更快的方法。 – Innovine 2013-04-23 11:07:05

+1

@Innovine,即使这样也不行,因为密钥不能保证按创建时间排序由规格。 – Dogbert 2013-04-23 11:09:16

+0

然后问题就变成了,我该如何将键对象按照固定的顺序串联起来......我不可能为(键名为obj)进行操作并将它们存储在临时数组中,然后将它们排序并手动构建一个字符串..但我绝望地希望有一个更简单的方法 – Innovine 2013-04-23 11:21:34

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

谢谢,但显然订购是不确定的,只发生工作。虽然有趣的方法! – Innovine 2013-04-23 11:28:03

3

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

在这里看到:

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

有,因为JSON数据是指通过按键来访问控制顺序没有内置的方法。

这里有一个小例子一个的jsfiddle:

http://jsfiddle.net/Eq2Yw/

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

编辑:

也看到这太问题关于字符串化的非确定性输出,尤其是农林渔业部的有关浏览器差异的详细信息:

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

+0

每个对象的属性都是已知的(并且大部分是字符串),所以在自己的toJSON字符串中对属性进行硬编码可能比排序快得多......! – Innovine 2013-04-23 11:37:37

+0

下面的'orderedJsonStringify'几乎奏效。但是这对我来说似乎是一个更好的解决方案。当我需要将C#DataContractJsonSerializer的'__type'作为json字符串中的第一项时。 var json = JSON.stringify(myjsonobj,['__type','id','text','somesubobj'etc .....]);列出所有的钥匙有点痛苦。 – 2016-09-09 19:12:03

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

+1

效果很好,但不幸的是它不是递归的。 – 2015-04-02 00:46:15

4

这是同萨泊尔·辛格的答案

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

我认为,如果你是在JSON发电控制(这听起来像你),然后为您的目的,这可能是一个很好的解决方案:json-stable-stringify

从项目网站:

确定性JSON.stringify()的自定义排序从字符串化的结果

如果产生的JSON是确定性得到 确定性的哈希值,你应该能够很容易地比较/合并。

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

'reorder'应该重命名为'sortObject',这不能处理数组 – 2015-04-02 00:42:50

+0

感谢您注意到,OP的问题是诉诸对象,而不是数组。 – 2015-04-02 13:23:37

+0

@JonathanGawrych无论如何,你为什么要对数组进行排序,他们应该有订单。数组[1,2,3]与数组[2,3,1]不同,但对象没有属性顺序。问题在于,顺序突然很重要 - 100%等价对象的字符串可能不同。不幸的是,获得确定性字符串化的努力似乎相当可观,例如:https://github.com/substack/json-stable-stringify/blob/master/index.js – 2016-11-25 14:46:09

5

更新:请检查基督教的answer。这比这更简单。


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

语法不太对。应该是函数orderedJsonStringify(o)返回JSON.stringify(Object.keys(o).sort()。reduce((r,k)=>(r [k] = o [k],r)) {})); } @ – Ben 2016-05-27 09:58:37

+0

@Ben固定,谢谢。 – aleung 2016-05-29 05:23:14

+0

现在,这已经解决了C#DataContractJsonSerializer和“__type”未列在json字符串中的第一个问题。谢谢。 – 2016-09-02 21:18:35

0

您可以在ECMAScript中排序属性名对象2015年

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

您可以通过属性名称为JSON.stringify()第二个参数的数组排序:

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

这种行为是否有任何形式的记录保证? – 2017-01-21 03:23:28

+1

@richremer是的,在[ECMAScript标准](http://www.ecma-international.org/ecma-262/6.0)中,JSON.stringify()的第二个参数被称为'replacer',可以是数组。它用于构建一个'PropertyList',然后在'SerializeJSONObject()'中使用,以便按照该顺序追加属性。这是从ECMAScript 5起记录的。 – 2017-01-21 04:35:41

+7

请注意,如果obj嵌套了对象,嵌套键也必须出现在第二个参数中;否则,他们将被丢弃! 反例如: '> JSON.stringify({一个:{C:1,B:2}},[ '一']) '{ “一”:{}}'' 一(哈克)构建所有相关键的列表的方法是使用JSON.stringify遍历对象: 'function orderedStringify(obj){ const allKeys = []; JSON.stringify(obj,(k,v)=> {allKeys.push(k); return v;}); return JSON.stringify(obj,allKeys.sort()); }' 实施例: '> orderedStringify({一个:{C:1,B:2}}) '{ “一”:{ “B”:2, “C”:1}}'' – 2017-04-05 17:24:05

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

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的行为,第一个键分配给对象i s首先输出JSON.stringify

+0

谢谢,但我有4年前的这个问题,我很高兴地说,自那时起我已经有点感动:) – Innovine 2017-08-08 19:20:05

+1

:)很高兴听到它。虽然我昨天遇到了这个问题,但是没有找到我在(老的但仍然相关的)问题下寻找的答案:) – AJP 2017-08-09 13:00:55

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,一个:[{Y:8,H:3},{一:3,b:7}]})

“{”F“:4,”a“:[{”h“:3,”j“:8},{”a“:3,”b “:7}]中,” b “:{” E “:9,” Y “:4,” z “的:2},” C “:9}”

+1

请为代码 – DeKaNszn 2017-08-17 08:43:05

+0

添加一些解释请解释为什么使用外部库,并且@DeKaNszn说,添加一些解释和介绍。这将不胜感激。 – Benj 2017-08-25 14:32:59

+0

这个函数是从我的项目中复制的。我在其中使用underscore.js。 – saintthor 2017-08-26 15:25:49

0

我把答案从@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}]}