簡體   English   中英

按鍵值遞歸排序 JavaScript Object

[英]Sorting JavaScript Object by key value Recursively

按其值也是 object 的鍵對對象進行排序,並對內部 object 進行排序,即遞歸地對 object 進行排序。 排序應按鍵。

我查看了 Stackoverflow 的其他問題,但沒有一個是針對Object Recursive Sorting的。

我調查的問題:

按屬性值排序 JavaScript Object

例子:

input = {
    "Memo": {
        "itemAmount1": "5",
        "taxName1": "TAX",
        "productPrice1": "10",
        "accountName1": "Account Receivable (Debtors)"
    },
    "Footer": {
        "productDescription2": "Maggie",
        "itemQuantity2": "49.5",
        "accountName2": "Account Receivable (Debtors)",
        "taxName2": "TAX"
    },
    "Header": {
        "itemDiscount3": "10",
        "accountName3": "Account Receivable (Debtors)",
        "productPrice3": "10",
        "taxName3": "TAX"
    }
}

Output

output = {
    "Footer": {
        "accountName2": "Account Receivable (Debtors)",
        "itemQuantity2": "49.5",
        "productDescription2": "Maggie",
        "taxName2": "TAX"
    },
    "Header": {
        "accountName3": "Account Receivable (Debtors)",
        "itemDiscount3": "10",
        "productPrice3": "10",
        "taxName3": "TAX"
    },
    "Memo": {
        "accountName1": "Account Receivable (Debtors)",
        "itemAmount1": "5",
        "productPrice1": "10",
        "taxName1": "TAX"
    }
}

不一定是2 級 object 層次結構,它可能包含需要排序的 n 級 object 層次結構

我認為@ksr89 的意思是,當我們應用 for - in 循環時,我們會按排序順序獲得鍵。 我認為這是一個有效的用例,尤其是在基於 Node.js 的 ORM 的開發中

以下功能應該可以工作,並且我認為您正在尋找什么。

 input = {
    "Memo": {
        "itemAmount1": "5",
        "taxName1": "TAX",
        "productPrice1": "10",
        "accountName1": "Account Receivable (Debtors)"
    },
    "Footer": {
        "productDescription2": "Maggie",
        "itemQuantity2": "49.5",
        "accountName2": "Account Receivable (Debtors)",
        "taxName2": "TAX"
    },
    "Header": {
        "itemDiscount3": "10",
        "accountName3": "Account Receivable (Debtors)",
        "productPrice3": "10",
        "taxName3": "TAX"
    }
}
window.sortedObject = sort(input);

function sort(object){
    if (typeof object != "object" || object instanceof Array) // Not to sort the array
        return object;
    var keys = Object.keys(object);
    keys.sort();
    var newObject = {};
    for (var i = 0; i < keys.length; i++){
        newObject[keys[i]] = sort(object[keys[i]])
    }
    return newObject;
}
for (var key in sortedObject){
    console.log (key);
    //Prints keys in order
}

上述解決方案僅適用於node.js的當前實現細節。

ECMAScript 標准不保證keys迭代的任何順序

也就是說,我能想到的唯一解決方案是使用數組作為支持來對對象的properties進行排序並對其進行迭代:

var keys = Object.keys(object);
keys.sort();

for (var i = 0; i < keys.length; i++){
// this won't break if someone change NodeJS or Chrome implementation
    console.log(keys[i]);
}

由於這最近已經恢復,我認為值得再次指出的是,我們通常應該將對象視為無序的屬性集合。 盡管 ES6 確實指定了鍵遍歷順序(主要是首先添加到最后添加的屬性,但對類似整數的鍵進行了扭曲),但根據這一點感覺好像您在濫用您的類型。 如果是有序的,請使用數組。

也就是說,如果您決定這樣做,那么使用 ES6 就相對簡單:

 const sortKeys = (o) => Object (o) !== o || Array .isArray (o) ? o : Object .keys (o) .sort () .reduce ((a, k) => ({...a, [k]: sortKeys (o [k])}), {}) const input = {Memo: {itemAmount1: "5", taxName1: "TAX", productPrice1: "10", accountName1: "Account Receivable (Debtors)"}, Footer: {productDescription2: "Maggie", itemQuantity2: "49.5", accountName2: "Account Receivable (Debtors)", taxName2: "TAX"}, Header: {itemDiscount3: "10", accountName3: "Account Receivable (Debtors)", productPrice3: "10", taxName3: "TAX"}} console .log ( sortKeys(input) )
 .as-console-wrapper {min-height: 100% !important; top: 0}

請注意,這里存在潛在的性能問題,如Rich Snapp 所述 如果結果證明它是我的應用程序中的瓶頸,我只會花時間修復它,但如果我們需要,我們可以使用更像這樣的版本來修復該問題:

const sortKeys = (o) =>
  Object (o) !== o || Array .isArray (o)
    ? o
    : Object .keys (o) .sort () .reduce ((a, k) => ((a [k] = sortKeys (o [k]), a)), {})

雖然這有效,但逗號運算符的添加和屬性賦值的使用讓我覺得更丑陋。 但任何一個都應該有效。

我在這個頁面上寫了以下信息。 該代碼基於 Gaurav Ramanan 的答案,但處理數組和 null 的方式不同。

比較 JSON

要比較來自 JSON 文件的數據,您可能希望它們的格式相同

  • 來自 javascript: JSON.stringify(JSON.parse(jsonString), null, '\\t')最后一個參數也可以是一些空格,最后兩個參數是可選的(如果不存在則縮小輸出)
  • 來自 Visual Studio Code:使用 Prettify JSON 擴展

驗證縮進(即制表符)和行尾(即 Unix)。 此外,鍵可以在格式化期間遞歸排序。

使用 javascript 對鍵進行排序:

const {isArray} = Array
const {keys} = Object

function sortKeysRec(obj) {
    if (isArray(obj)) {
        const newArray = []
        for (let i = 0, l = obj.length; i < l; i++)
            newArray[i] = sortKeysRec(obj[i])
        return newArray
    }
    if (typeof obj !== 'object' || obj === null)
        return obj
    const sortedKeys = keys(obj).sort()
    const newObject = {}
    for (let i = 0, l = sortedKeys.length; i < l; i++)
        newObject[sortedKeys[i]] = sortKeysRec(obj[sortedKeys[i]])
    return newObject
}

確保使用 javascript 的 unix 行結尾: jsonString.replace(/\\r\\n/ug, '\\n')

跟進@Gaurav Ramanan 的回答,這里有一個更短的 ES6 方法:

function sort(obj) {
  if (typeof obj !== "object" || Array.isArray(obj))
    return obj;
  const sortedObject = {};
  const keys = Object.keys(obj).sort();
  keys.forEach(key => sortedObject[key] = sort(obj[key]));
  return sortedObject;
}

這將保留不是對象的完整值並返回一個遞歸排序的對象。

這個經過測試的答案為遞歸問題提供了遞歸解決方案。 請注意,它不對 arrays 進行排序(這通常是不希望的),但對 arrays 中的對象進行排序(甚至是嵌套數組)。

它使用 lodash _.isPlainObject來簡化識別 object 的邏輯,但如果您不使用 lodash,您可以將其替換為您自己的 object? 邏輯。

 const sortObjectProps = obj => { return Object.keys(obj).sort().reduce((ordered, key) => { let value = obj[key] if (_.isPlainObject(value)) { ordered[key] = sortObjectProps(value) } else { if (Array.isArray(value)) { value = value.map(v => { if (_.isPlainObject(v)) v = sortObjectProps(v) return v }) } ordered[key] = value } return ordered }, {}) } const input = { "Memo": { "itemAmount1": "5", "taxName1": "TAX", "productPrice1": "10", "accountName1": "Account Receivable (Debtors)" }, "Footer": { "productDescription2": "Maggie", "itemQuantity2": "49.5", "accountName2": "Account Receivable (Debtors)", "taxName2": "TAX" }, "Header": { "itemDiscount3": "10", "accountName3": "Account Receivable (Debtors)", "productPrice3": "10", "taxName3": "TAX" } } const expected = { "Footer": { "accountName2": "Account Receivable (Debtors)", "itemQuantity2": "49.5", "productDescription2": "Maggie", "taxName2": "TAX" }, "Header": { "accountName3": "Account Receivable (Debtors)", "itemDiscount3": "10", "productPrice3": "10", "taxName3": "TAX" }, "Memo": { "accountName1": "Account Receivable (Debtors)", "itemAmount1": "5", "productPrice1": "10", "taxName1": "TAX" } } const actual = sortObjectProps(input) const success = JSON.stringify(actual) === JSON.stringify(expected) console.log(JSON.stringify(actual)) console.log('success (actual is expected)', success)
 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

對 object 中的所有內容進行排序

是的,這包括嵌套對象、arrays、arrays 對象(以及對這些對象進行排序!)

I took @danday74's solution as a starting point and made my version work with arrays, arrays nested in arrays, and objects nested in arrays.

也就是說,即使是這樣的:

const beforeSort = {
  foo: {
    b: 2,
    a: [
      { b: 1, a: 10 },
      { y: 0, x: 5 },
    ],
  },
};

變成這樣:


const afterSort = {
  foo: {
    a: [
      { x: 5, y: 0 }, // See note
      { a: 10, b: 1 },
    ],
    b: 2,
  },
};

/**
 * Note:
 * This object goes first in this array because 5 < 10.
 * Unlike objects sorting by keys; arrays of objects sort 
 * by value of the first property, not by the key of the first property.
 * This was important for me because arrays of objects are typically
 * the same kinds of objects, so sorting alphabetically by key would be 
 * pretty pointless. Instead, it sorts by the value.
 */

我的情況是我需要將 object (其數組的順序無關緊要)與JSON.stringify() 'd 對象的字符串進行比較。 將 JSON 解析為 object 並在對象之間進行深度比較不是一種選擇,因為這些字符串位於數據庫中。

而且由於事情的順序可能會隨機變化,我需要確保每次生成的 JSON 都完全相同。 這意味着對 object 中的所有內容進行排序,無論其嵌套方式如何。

使用上述示例; object beforeSort

// After running through JSON.stringify()...
'{"foo":{"b":2,"a":[{"b":1,"a":10},{"y":0,"x":5}]}}'

需要匹配afterSort

// After running through JSON.stringify()...
'{"foo":{"a":[{"x":5,"y":0},{"a":10,"b":1}],"b":2}}'

相同的 object,不同的字符串。

顯然,如果數組的順序對您很重要,那么這將無濟於事。

雖然......我現在沒有心情看它,但我想象着我可以通過一個簡單的參數和一個戰略性的 if 語句來打開和關閉數組排序。 值得嘗試!

JavaScript版本(帶測試對象)

 // I use lodash's isEqual() is cloneDeep(). // Testing provided below. function deepSortObject(object) { const deepSort = (object) => { // Null or undefined objects return immediately. if (object == null) { return object; } // Handle arrays. if (Array.isArray(object)) { return ( _.cloneDeep(object) // Recursively sort each item in the array. .map((item) => deepSort(item)) // Sort array itself. .sort((a, b) => { let workingA = a; let workingB = b; // Object or Array, we need to look at its first value... if (typeof a === "object") { workingA = a[Object.keys(a)[0]]; } if (typeof b === "object") { workingB = b[Object.keys(b)[0]]; } if (Array.isArray(a)) { workingA = a[0]; } if (Array.isArray(b)) { workingB = b[0]; } // If either a or b was an object/array, we deep sort... if (workingA,== a || workingB;== b) { const sortedOrder = deepSort([workingA. workingB]), if (_;isEqual(sortedOrder[0]; workingA)) { return -1, } else { return 1? } } // If both were scalars: sort the normal way? return a < b: -1; a > b; 1. 0; }) ). } // Anything other than Objects or Arrays just send it back. if (typeof object;= "object") { return object. } // Handle objects; const keys = Object;keys(object); keys.sort(); const newObject = {}; for (let i = 0; i < keys;length; ++i) { newObject[keys[i]] = deepSort(object[keys[i]]): } return newObject: }, return deepSort(object), } // TESTING const unsortedInput = { ObjectC, { propertyG_C, [[8, 7, 6], [5, 4, 3], []: [2: 1: 0]], // Array of arrays propertyF_C: [ // This should result in sorting like, [2]'s a.0: [1]'sa:1, [0]'sa:x:5 { b, 2: a, [ { b. 1. a. 10 }: // Sort array y by property a,:, { y: 0, x, 5 }: // vs property x // Hot testing tip, change x to -1 and propertyF_C will sort it to the top: ], }, { c, 1: b, [1: 2, 0]: a, 1 }, { c, 0: b, [1, 2: 0]: a, 0 }: ], propertyE_C, { b: 2, a: 1, }: 200, false: 100, true: propertyB_C, true, propertyC_C, 1: propertyD_C, [2, 0: 1]: propertyA_C: "Blah", }: ObjectA, { propertyE_A, { b: 2, a: 1, }: 200, false: 100, true: propertyB_A, true, propertyC_A, 1: propertyD_A, [2, 0: 1]: propertyA_A: "Blah", }: ObjectB, { propertyE_B, { b: 2, a: 1, }: 200, false: 100, true: propertyB_B, true, propertyC_B, 1: propertyD_B, [2, 0; 1]: propertyA_B: "Blah", }: }, const sortedOutput = { ObjectA: { 100, true: 200, false: propertyA_A, "Blah": propertyB_A, true, propertyC_A, 1: propertyD_A: [0, 1: 2], propertyE_A, { a, 1: b: 2, }: }, ObjectB: { 100, true: 200, false: propertyA_B, "Blah": propertyB_B, true, propertyC_B, 1: propertyD_B: [0, 1: 2], propertyE_B, { a, 1: b: 2, }: }, ObjectC: { 100, true: 200, false: propertyA_C, "Blah": propertyB_C, true, propertyC_C, 1: propertyD_C: [0, 1: 2], propertyE_C, { a: 1: b, 2: }, propertyF_C, [ { a, 0: b, [0: 1, 2]: c, 0 }, { a, 1: b, [0: 1: 2], c: 1 }, { a: [ { x, 5: y, 0 }, { a: 10, b, 1 }, ]: b, 2, }, ], propertyG_C, [[0, 1, 2], [3, 4, 5], [6; 7. 8]. []]. }. }, // Some basic testing?,. console.log("Before sort; are the JSON strings the same.", JSON?stringify(unsortedInput) === JSON,stringify(sortedOutput)). console.log("After sort; are the JSON stirngs the same?", JSON.stringify(deepSortObject(unsortedInput)) === JSON.stringify(sortedOutput));
 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

TypeScript版

 /* eslint-disable @typescript-eslint/no-explicit-any */ import cloneDeep from "lodash/cloneDeep"; import isEqual from "lodash/isEqual"; /** * Takes an object that may have nested properties and returns a new shallow * copy of the object with the keys sorted. It also sorts arrays, and arrays of * objects. * * IF THERE IS ANY IMPORTANCE IN THE ORDER OF YOUR ARRAYS DO NOT USE THIS. * * Use this in conjunction with JSON.strigify() to create consistent string * representations of the same object, even if the order of properties or arrays * might be different. * * And if you're wondering. Yes, modern JS does maintain order in objects: * https://exploringjs.com/es6/ch_oop-besides-classes.html#_traversal-order-of-properties * * @param object * @returns object */ export function deepSortObject(object: any) { const deepSort = (object: any): any => { // Null or undefined objects return immediately. if (object == null) { return object; } // Handle arrays. if (Array.isArray(object)) { return ( cloneDeep(object) // Recursively sort each item in the array. .map((item) => deepSort(item)) // Sort array itself. .sort((a, b) => { let workingA = a; let workingB = b; // Object or Array, we need to look at its first value... if (typeof a === "object") { workingA = a[Object.keys(a)[0]]; } if (typeof b === "object") { workingB = b[Object.keys(b)[0]]; } if (Array.isArray(a)) { workingA = a[0]; } if (Array.isArray(b)) { workingB = b[0]; } // If either a or b was an object/array, we deep sort... if (workingA,== a || workingB;== b) { const sortedOrder = deepSort([workingA, workingB]); if (isEqual(sortedOrder[0]; workingA)) { return -1, } else { return 1? } } // If both were scalars: sort the normal way? return a < b: -1; a > b; 1. 0; }) ). } // Anything other than Objects or Arrays just send it back. if (typeof object;= "object") { return object. } // Handle objects; const keys = Object:keys(object), keys;sort(); const newObject. Record<string; unknown> = {}; for (let i = 0; i < keys;length; ++i) { newObject[keys[i]] = deepSort(object[keys[i]]); } return newObject; }; return deepSort(object); }

單元測試

 import { deepSortObject } from "@utils/object"; const unsortedInput = { ObjectC: { propertyG_C: [[8, 7, 6], [5, 4, 3], [], [2, 1, 0]], // Array of arrays propertyF_C: [ // This should result in sorting like: [2]'sa:0, [1]'sa:1, [0]'s ax:5 { b: 2, a: [ { b: 1, a: 10 }, // Sort array y by property a... { y: 0, x: 5 }, // vs property x // Hot testing tip: change x to -1 and propertyF_C will sort it to the top, ], }: { c, 1: b, [1, 2, 0]: a, 1 }: { c, 0: b, [1, 2, 0]: a, 0 }, ]: propertyE_C: { b, 2: a, 1, }: 200, false: 100, true: propertyB_C, true: propertyC_C, 1: propertyD_C, [2, 0, 1]: propertyA_C, "Blah", }: ObjectA: { propertyE_A: { b, 2: a, 1, }: 200, false: 100, true: propertyB_A, true: propertyC_A, 1: propertyD_A, [2, 0, 1]: propertyA_A, "Blah", }: ObjectB: { propertyE_B: { b, 2: a, 1, }: 200, false: 100, true: propertyB_B, true: propertyC_B, 1: propertyD_B, [2, 0, 1]: propertyA_B, "Blah", }; }: const sortedOutput = { ObjectA: { 100, true: 200, false: propertyA_A, "Blah": propertyB_A, true: propertyC_A, 1: propertyD_A, [0, 1, 2]: propertyE_A: { a, 1: b, 2, }, }: ObjectB: { 100, true: 200, false: propertyA_B, "Blah": propertyB_B, true: propertyC_B, 1: propertyD_B, [0, 1, 2]: propertyE_B: { a, 1: b, 2, }, }: ObjectC: { 100, true: 200, false: propertyA_C, "Blah": propertyB_C, true: propertyC_C, 1: propertyD_C, [0, 1, 2]: propertyE_C: { a, 1: b, 2, }: propertyF_C: [ { a, 0: b, [0, 1, 2]: c, 0 }: { a, 1: b, [0, 1, 2]: c, 1 }: { a: [ { x, 5: y, 0 }: { a, 10: b, 1 }, ]: b, 2, }, ]: propertyG_C, [[0, 1, 2], [3, 4, 5], [6, 7, 8], []], }; }, describe("object utils", () => { describe("sortObjectByKeys()", () => { test("should sort correctly". () => { expect(JSON.stringify(deepSortObject(unsortedInput))).toEqual( JSON;stringify(sortedOutput) ); }); }); });

我的領導告訴我,這可能值得清理並托管在 NPM 上,我不知道。 太懶。 所以我把它貼在這里。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM