简体   繁体   English

如何通过嵌套的 object 属性对 JavaScript 对象数组进行排序?

[英]How to sort a JavaScript array of objects by nested object property?

I have this function to sort a JavaScript array of objects based on a property:我有这个 function 来根据属性对 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;
        }
    });
};

It works with arrays like this:它像这样与 arrays 一起工作:

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

But I want to be able to sort also by nested properties, for example something like:但我也希望能够按嵌套属性进行排序,例如:

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

However this doesn't work because it is not possible to do something like object['nestedobj.property'] , it should be object['nestedobj']['property'] .然而,这不起作用,因为不可能做类似object['nestedobj.property']事情,它应该是object['nestedobj']['property']

Do you know how could I solve this problem and make my function work with properties of nested objects?你知道我如何解决这个问题并让我的 function 使用嵌套对象的属性吗?

Thanks in advance提前致谢

You can split the prop on .prop. , and iterate over the Array updating the a and b with the next nested property during each iteration. , 并在每次迭代期间使用下一个嵌套属性更新ab的数组。

Example: http://jsfiddle.net/x8KD6/1/示例: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;
};

Instead of passing the property as a string, pass a function that can retrieve the property from the top level object.不要将属性作为字符串传递,而是传递一个可以从顶级对象检索属性的函数。

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

Invoke as,调用为,

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

sort(simplePropertyRetriever, { .. });

Or using a nested object,或者使用嵌套对象,

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

sort(nestedPropertyRetriever, { .. });

Use Array.prototype.sort() with a custom compare function to do the descending sort first:使用Array.prototype.sort()和自定义比较函数首先进行降序排序:

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

Even nicer with ES6:使用 ES6 更好:

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

if you have array of objects like如果你有像这样的对象数组

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
        }
    }];

you can use simply你可以简单地使用

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

and call it并称之为

for direct objects对于直接对象

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

for nested objects对于嵌套对象

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

You can use Agile.js for this kind of things.您可以将Agile.js用于此类事情。
Actually you pass an expression instead of callback, it's handle nested properties and javascript expression in a very nice-ish way.实际上你传递一个表达式而不是回调,它以一种非常好的方式处理嵌套属性和 javascript 表达式。

Usage: _.orderBy(array, expression/callback, reverse[optional])用法: _. _.orderBy(array, expression/callback, reverse[optional])

Example:例子:

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]]

Would this meet your needs?这能满足您的需求吗?

// 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;
        }
    });
};

Try this (used a recursive function to get nested value, you can pass the nested property as nestedobj.property): You can use this for any level of hierarchy试试这个(使用递归函数来获取嵌套值,您可以将嵌套属性作为 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;
        }
    });
};

This is my modify code.这是我的修改代码。

// 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;
        }
    });
};

 var objectsArr = [ {nestedobj:{property:'1'}}, {nestedobj:{property:'3'}}, {nestedobj:{property:'2'}}, {nestedobj:{property:'4'}} ]; function getFromPath(obj, path) { let r = obj; path.forEach(key => { r = r[key]}) return r } function sortObjectsArr(objectsArray, ...path) { objectsArray.sort((a, b) => getFromPath(a, path) - getFromPath(b, path)) } sortObjectsArr(objectsArr, 'nestedobj', 'property'); console.log(objectsArr);

Unfortunately, I didn't find any nice way to use the arguments in order to access the attributes of the nested object.不幸的是,我没有找到任何好的方法来使用参数来访问嵌套对象的属性。
Want to mention that there can be some checks if the keys are available in the passed object, but this depends on who and how want to implement this.想提一下,如果键在传递的对象中可用,可以进行一些检查,但这取决于谁以及如何实现这一点。

Description描述

My solution is this one.我的解决方案是这个。 I decide to flat the object first:我决定先将物体展平:

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

And then I'll extract the value from the object:然后我将从对象中提取值:

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

Ant at the end I just need to do this:蚂蚁最后我只需要这样做:

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

Finally we need to do this:最后我们需要这样做:

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

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

I think this way is much much clear and cleaner.我认为这种方式要清晰得多。 It is readable and more functional.它具有可读性和功能性。 I merge multiple answer from stackoverflow to reach this solution :sweat_smile:我合并了来自stackoverflow的多个答案以达到这个解决方案:sweat_smile:

This should work and can accept multiple parameters.这应该可以工作并且可以接受多个参数。

https://codepen.io/allenACR/pen/VwQKWZG 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})

3 levels deep. 3层深。 path can look like this.路径可能看起来像这样。 'level1' or 'level1.level2' or 'level1.level2.level3' I also did uppercase for the sort all my items are strings. 'level1' 或 'level1.level2' 或 'level1.level2.level3' 我也做了大写的排序我的所有项目都是字符串。 Anwser is a modified version from - @Mas Anwser 是来自 - @Mas 的修改版本

public keysrt(arr: Object[], path: string, reverse: boolean): void {
    const nextOrder = reverse ? -1 : 1;
    const pathSplit = path.split('.');
    if (arr === null || arr === undefined ) {
        return;
    }
    if (arr.length <= 1) {
        return;
    }

    const nestedSort = (prop1, prop2 = null, prop3 = null, direction = 'asc') => (e1, e2) => {
        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;
        return (a.toString().toUpperCase() < b.toString().toUpperCase()) ?
            -sortOrder * nextOrder : (a.toString().toUpperCase() > b.toString().toUpperCase()) ?
                sortOrder * nextOrder : 0;
    };

    if (pathSplit.length === 3) {
        arr.sort(nestedSort(pathSplit[0], pathSplit[1], pathSplit[2]));
    }
    if (pathSplit.length === 2) {
        arr.sort(nestedSort(pathSplit[0], pathSplit[1]));
    }
    if (pathSplit.length === 1) {
        arr.sort(nestedSort(pathSplit[0], null));
    }
}

For those who a researching on how to sort nested properties.对于那些正在研究如何对嵌套属性进行排序的人。 I created a small type-safe array sorting method with support for deeply nested properties and Typescript autocompletion.我创建了一个类型安全的小型数组排序方法,支持深度嵌套属性和 Typescript 自动完成。

https://github.com/jvandenaardweg/sort-by-property https://github.com/jvandenaardweg/sort-by-property

https://www.npmjs.com/package/sort-by-property https://www.npmjs.com/package/sort-by-property

Example:例子:

blogPosts.sort(sortByProperty('author.name', 'asc'));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM