繁体   English   中英

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

[英]How to sort a JavaScript array of objects by nested object 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;
        }
    });
};

它像这样与 arrays 一起工作:

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

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

提前致谢

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

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

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, { .. });

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

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

使用 ES6 更好:

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

如果你有像这样的对象数组

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

您可以将Agile.js用于此类事情。
实际上你传递一个表达式而不是回调,它以一种非常好的方式处理嵌套属性和 javascript 表达式。

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

例子:

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

这能满足您的需求吗?

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

试试这个(使用递归函数来获取嵌套值,您可以将嵌套属性作为 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;
        }
    });
};

这是我的修改代码。

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

不幸的是,我没有找到任何好的方法来使用参数来访问嵌套对象的属性。
想提一下,如果键在传递的对象中可用,可以进行一些检查,但这取决于谁以及如何实现这一点。

描述

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

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:

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

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层深。 路径可能看起来像这样。 'level1' 或 'level1.level2' 或 'level1.level2.level3' 我也做了大写的排序我的所有项目都是字符串。 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));
    }
}

对于那些正在研究如何对嵌套属性进行排序的人。 我创建了一个类型安全的小型数组排序方法,支持深度嵌套属性和 Typescript 自动完成。

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

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

例子:

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

暂无
暂无

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

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