按键值递归排序JavaScript对象

Sorting JavaScript Object by key value Recursively

本文关键字:JavaScript 对象 排序 递归 键值      更新时间:2023-09-26

按值也是对象的键对对象进行排序,并对该内部对象进行排序,即对对象进行递归排序。按键排序

我查看了Stackoverflow的其他问题,但是没有是针对对象递归排序的。

我调查的问题:

按属性值排序JavaScript对象

例子:

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 = {
    "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级对象层次,可以包含n级对象层次需要排序。

我认为@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
}

我在这一页上写了以下信息。该代码基于Gaurav Ramanan的答案,但处理数组和null的方式不同。

<标题>比较JSON h1> 要比较JSON文件中的数据,您可能希望以相同的方式格式化它们

  • from javascript: JSON.stringify(JSON.parse(jsonString), null, ''t')最后一个参数也可以是一些空格最后两个参数是可选的(如果没有则输出最小化)
  • 从Visual Studio Code:与美化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
}

确保unix行以javascript: 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;
}

第一个条件只是确保您只解析一个有效的对象。因此,如果不是,它将立即返回原始值不变。

然后赋值一个空对象,因为它将在forEach循环中使用,在forEach循环中,它将根据最终排序结果进行变异。

输出将是一个递归排序对象。

上述解决方案仅适用于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)), {})

虽然这可以工作,但在我看来,添加逗号操作符和使用属性分配使它更难看。但任何一种都应该有效。

这个经过测试的答案为递归问题提供了递归解决方案。注意,它不排序数组(这通常是不希望的),但排序数组中的对象(甚至嵌套数组)。

它使用lodash _.isPlainObject来简化识别对象的逻辑,但是如果你不使用lodash,你可以用你自己的lodash替换它,它是一个对象吗?逻辑。

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>

对对象中的所有内容进行排序

是的,这包括嵌套对象、数组、对象的数组(以及对这些对象排序!)

我以@danday74的解决方案为起点,并使我的版本使用数组,嵌套在数组中的数组和嵌套在数组中的对象。

也就是说,甚至像这样:

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.
 */

我的情况是,我需要比较一个对象(其数组的顺序无关紧要)和JSON.stringify() 'd对象的字符串。解析JSON为对象并在对象之间执行深度比较是不可能的,因为这些字符串在数据库中。

由于事物的顺序可能随机改变,我需要确保每次生成的JSON都完全相同。这意味着对对象中的所有内容进行排序,无论嵌套如何。

用上面的例子;对象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}}'

(相同的对象,不同的字符串。)

显然,如果数组的顺序对您很重要,那么这将没有帮助。

虽然……我现在没有心情看它,我想象我可以用一个简单的参数和一个战略性的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]'s a:1, [0]'s a.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>

<

打印稿版本/h3>

/* 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]'s a:0, [1]'s a:1, [0]'s a.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], []],
  },
};
describe("object utils", () => {
  describe("sortObjectByKeys()", () => {
    test("should sort correctly", () => {
      expect(JSON.stringify(deepSortObject(unsortedInput))).toEqual(
        JSON.stringify(sortedOutput)
      );
    });
  });
});

我的领导告诉我这可能值得清理和托管在NPM,我不知道。太懒惰。所以我把它贴在了这里