2013-04-23 170 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}]}