如何按嵌套对象属性对JavaScript数组对象进行排序?

52

我有一个函数,用于根据属性对JavaScript对象数组进行排序:

// arr is the array of objects, prop is the property to sort by
var sort = function (prop, arr) {
    arr.sort(function (a, b) {
        if (a[prop] < b[prop]) {
            return -1;
        } else if (a[prop] > b[prop]) {
            return 1;
        } else {
            return 0;
        }
    });
};

它适用于这样的数组:

sort('property', [
    {property:'1'},
    {property:'3'},
    {property:'2'},
    {property:'4'},
]);

但我也想能够按嵌套属性排序,例如:

sort('nestedobj.property', [
    {nestedobj:{property:'1'}},
    {nestedobj:{property:'3'}},
    {nestedobj:{property:'2'}},
    {nestedobj:{property:'4'}}
]);

然而这种方式并不起作用,因为无法像object['nestedobj.property']这样做,应该是object['nestedobj']['property']

你知道如何解决这个问题并使我的函数能够使用嵌套对象的属性吗?

提前致谢。

13个回答

44
你可以根据 . prop分割成数组,并在每次迭代中更新ab为下一个嵌套属性。 示例: http://jsfiddle.net/x8KD6/1/
var sort = function (prop, arr) {
    prop = prop.split('.');
    var len = prop.length;

    arr.sort(function (a, b) {
        var i = 0;
        while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
        if (a < b) {
            return -1;
        } else if (a > b) {
            return 1;
        } else {
            return 0;
        }
    });
    return arr;
};

谢谢,这个方法可行且比其他建议的选项更快。 - VerizonW
@VerizonW:很高兴它起作用了。是的,如果可以的话,我喜欢避免函数调用。这样可以保持代码的流畅性。:o) - user113716
这似乎无法处理属性值长度不同的情况。例如:{nestedobj:{property:'g'}}, {nestedobj:{property:'F'}}, {nestedobj:{property:'abcd'}}, {nestedobj:{property:'abba'}} - Alan Wells

13

使用自定义比较函数和 Array.prototype.sort() 以进行降序排序:

champions.sort(function(a, b) { return b.level - a.level }).slice(...

使用ES6更加优美:

champions.sort((a, b) => b.level - a.level).slice(...

8

不要将属性作为字符串传递,而应该传递一个可以从顶级对象中检索属性的函数。

var sort = function (propertyRetriever, arr) {
    arr.sort(function (a, b) {
        var valueA = propertyRetriever(a);
        var valueB = propertyRetriever(b);

        if (valueA < valueB) {
            return -1;
        } else if (valueA > valueB) {
            return 1;
        } else {
            return 0;
        }
    });
};

调用方式如下:

var simplePropertyRetriever = function(obj) {
    return obj.property;
};

sort(simplePropertyRetriever, { .. });

或者使用嵌套对象,
var nestedPropertyRetriever = function(obj) {
    return obj.nestedObj.property;
};

sort(nestedPropertyRetriever, { .. });

7
如果您有一个对象数组,例如:
const objs = [{
        first_nom: 'Lazslo',
        last_nom: 'Jamf',
        moreDetails: {
            age: 20
        }
    }, {
        first_nom: 'Pig',
        last_nom: 'Bodine',
        moreDetails: {
            age: 21
        }
    }, {
        first_nom: 'Pirate',
        last_nom: 'Prentice',
        moreDetails: {
            age: 22
        }
    }];

你可以简单地使用。
nestedSort = (prop1, prop2 = null, direction = 'asc') => (e1, e2) => {
        const a = prop2 ? e1[prop1][prop2] : e1[prop1],
            b = prop2 ? e2[prop1][prop2] : e2[prop1],
            sortOrder = direction === "asc" ? 1 : -1
        return (a < b) ? -sortOrder : (a > b) ? sortOrder : 0;
    }

并将其称为

直接对象

objs.sort(nestedSort("last_nom"));
objs.sort(nestedSort("last_nom", null, "desc"));

对于嵌套对象

objs.sort(nestedSort("moreDetails", "age"));
objs.sort(nestedSort("moreDetails", "age", "desc"));

在所有这些解决方案中,我使用了你的并且它运行得很好。我确实不得不更改代码以添加第三层深度。 - howserss
1
这是我所做的 -> const a = prop3 ? e1[prop1][prop2][prop3] : prop2 ? e1[prop1][prop2] : e1[prop1], b = prop3 ? e2[prop1][prop2][prop3] : prop2 ? e2[prop1][prop2] : e2[prop1], sortOrder = direction === 'asc' ? 1 : -1; - howserss
谢谢您提供通用解决方案。我现在正在使用它。但是,我总是觉得很难理解以柯里化格式编写的代码。有没有其他方法可以实现相同的功能呢?@Mas - Swapnil Mhaske
@SwapnilMhaske 你可以使用https://es6console.com/将ES6转换为ES5代码,可能会像这样: nestedSort = function nestedSort(prop1) { var prop2 = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; var direction = arguments.length <= 2 || arguments[2] === undefined ? 'asc' : arguments[2]; return function (e1, e2) { var a = prop2 ? e1[prop1][prop2] : e1[prop1], b = prop2 ? e2[prop1][prop2] : e2[prop1], sortOrder = direction === "asc" ? 1 : -1; return a < b ? -sortOrder : a > b ? sortOrder : 0; }; }; - Mas

4
你可以使用Agile.js来完成这种操作。
实际上,你可以传递一个表达式而不是回调函数,它能够处理嵌套属性和JavaScript表达式,使其更加易于理解。

使用方法:_.orderBy(array, expression/callback, reverse[可选])

示例:

var orders = [
  { product: { price: 91.12, id: 1 }, date: new Date('01/01/2014') },
  { product: { price: 79.21, id: 2 }, date: new Date('01/01/2014') },
  { product: { price: 99.90, id: 3 }, date: new Date('01/01/2013') },
  { product: { price: 19.99, id: 4 }, date: new Date('01/01/1970') }
];

_.orderBy(orders, 'product.price');
// →  [orders[3], orders[1], orders[0], orders[2]]

_.orderBy(orders, '-product.price');
// → [orders[2], orders[0], orders[1], orders[3]]

1

这个是否符合您的需求?

// arr is the array of objects, prop is the property to sort by
var sort = function (nestedObj, prop, arr) {
    arr.sort(function (a, b) {
        if (a[nestedObj][prop] < b[nestedObj][prop]) {
            return -1;
        } else if (a[nestedObj][prop] > b[nestedObj][prop]) {
            return 1;
        } else {
            return 0;
        }
    });
};

0
尝试使用这个(使用递归函数获取嵌套值,可以将嵌套属性作为nestedobj.property传递): 您可以在任何层次结构中使用它。
// arr is the array of objects, prop is the property to sort by
var getProperty = function(obj, propNested){
 if(!obj || !propNested){
  return null;
 }
 else if(propNested.length == 1) {
    var key = propNested[0];
    return obj[key];
 }
 else {
  var newObj = propNested.shift();
    return getProperty(obj[newObj], propNested);
 }
};
var sort = function (prop, arr) {
    arr.sort(function (a, b) {
                var aProp = getProperty(a, prop.split("."));
                var bProp = getProperty(a, prop.split("."));
        if (aProp < bProp) {
            return -1;
        } else if (aProp > bProp) {
            return 1;
        } else {
            return 0;
        }
    });
};

0

这是我修改后的代码。

// arr is the array of objects, prop is the property to sort by
var s = function (prop, arr) {
    // add sub function for get value from obj (1/2)
    var _getVal = function(o, key){
        var v = o;
        var k = key.split(".");
        for(var i in k){
            v = v[k[i]];
        }
        return v;
    }
    return arr.sort(function (a, b) {
        // get value from obj a, b before sort (2/2)
        var aVal = _getVal(a, prop);
        var bVal = _getVal(b, prop);
        if (aVal < bVal) {
            return -1;
        } else if (aVal > bVal) {
            return 1;
        } else {
            return 0;
        }
    });
};

0

描述

我的解决方案是这个。我决定先将对象展平:

function flattenObject(value: any): any {
    let toReturn: any = {};

    for (const i in value) {
        if (!value.hasOwnProperty(i)) {
            continue;
        }

        if (typeof value[i] == 'object') {
            const flatObject = flattenObject(value[i]);
            for (const x in flatObject) {
                if (!flatObject.hasOwnProperty(x)) continue;

                toReturn[i + '.' + x] = flatObject[x];
            }
        } else {
            toReturn[i] = value[i];
        }
    }
    return toReturn;
}

然后我将从对象中提取值:

function nestedFieldValue(
    nestedJoinedFieldByDot: string,
    obj: any,
): any {
    return flattenObject(obj)[nestedJoinedFieldByDot];
}

最后我只需要这样做:

export function fieldSorter(fields: string[]) {
    return function (a: any, b: any) {
        return fields
            .map(function (fieldKey) {
                // README: Sort Ascending by default
                let dir = 1;

                if (fieldKey[0] === '-') {
                    // README: Sort Descending if `-` was passed at the beginning of the field name
                    dir = -1;
                    fieldKey = fieldKey.substring(1);
                }

                const aValue = nestedFlattenObjectFieldValue(
                    fieldKey,
                    a,
                );
                const bValue = nestedFlattenObjectFieldValue(
                    fieldKey,
                    b,
                );

                if (
                    typeof aValue === 'number' ||
                    typeof bValue === 'number'
                ) {
                    /**
                     * README: default value when the field does not exists to prevent unsorted array
                     * I assume that 0 should be the last element. In other word I sort arrays in a way
                     * that biggest numbers comes first and then smallest numbers
                     */
                    if (aValue ?? 0 > bValue ?? 0) {
                        return dir;
                    }
                    if (aValue ?? 0 < bValue ?? 0) {
                        return -dir;
                    }
                } else {
                    if (aValue ?? 0 > bValue ?? 0) {
                        return dir;
                    }
                    if (aValue ?? 0 < bValue ?? 0) {
                        return -dir;
                    }
                }

                return 0;
            })
            .reduce(function firstNonZeroValue(p, n) {
                return p ? p : n;
            }, 0);
    };
}

最后我们需要做这个:

const unsorted = [ 
    { 
        city: { 
            priority: 1, 
            name: 'Tokyo', 
            airport: { name: 'Haneda Airport' } 
        }
    }
]

const result = unsorted.sort(
    fieldSorter(['city.priority', 'city.airport.name', 'city.name']),
);

我认为这种方式更加清晰和简洁。它易读且更加实用。我合并了来自stackoverflow的多个答案以达到这个解决方案:sweat_smile:


0

这应该可以工作,并且可以接受多个参数。

https://codepen.io/allenACR/pen/VwQKWZG

function sortArrayOfObjects(items, getter) {
  const copy = JSON.parse(JSON.stringify(items));

  const sortFn = fn => {
    copy.sort((a, b) => {
      a = fn(a)
      b = fn(b)
      return a === b ? 0 : a < b ? -1 : 1;
    });
  };

  getter.forEach(x => {
    const fn = typeof x === 'function' ? x : item => item[x];
    sortFn(fn);
  });

  return copy;
}

// example dataset
const data = [
  {id: 3, name: "Dave", details: {skill: "leader"} },
  {id: 1, name: "Razor", details: {skill: "music"} }, 
  {id: 2, name: "Syd", details: {skill: "animal husbandry"} }
]

// sort via single prop
const sort1 = sortArrayOfObjects(data, ["id"])
// returns [Razor, Syd, Dave]

// sort via nested
const sort2 = sortArrayOfObjects(data, [
  (item) => item.details.skill  
])
// returns [Syd, Dave, Razor]

console.log({sort1, sort2})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接