2

I have an array of movies that I am trying to sort based on a dynamic value labeled 'path' which evaluates to either 'title', 'genre.name', 'numberInStock', or 'dailyRentalRate' depending on the current state. However, because the 'genre' property within the movies array is an object and not a string like the others, the compare function I wrote tries to access movieA("genre.name") and movieB("genre.name"). I thought this syntax might work but it does not. Surely there must an elegant way to write my compare function that does not require just adding more conditionals for when path is set to 'genre.name"? Any help or insight is greatly appreciated. Thanks. (Below is some code snippets)

var movies = [
    {
        title: "Terminator",
        genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
        numberInStock: 6,
        dailyRentalRate: 2.5,
    },
    {
        title: "Die Hard",
        genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
        numberInStock: 5,
        dailyRentalRate: 2.5
    },
    {
        title: "Get Out",
        genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
        numberInStock: 8,
        dailyRentalRate: 3.5
    }
]

this.state = {
    path: "title"  //'title' or 'genre.name' or 'numberInStock' or 'dailyRentalRate'
};

myCompare = (a, b) => {
    const path  = this.state.path;

    if (a[path] < b[path]) return - 1;
    if (a[path] > b[path]) return 1;
    return 0;
}

const moviesSorted = movies.sort(this.myCompare);
4Matt
  • 241
  • 1
  • 7

2 Answers2

3

When the path contains a period, you should turn it into an array with split, then use reduce to iterate over it and find the nested value you're trying to sort by:

var movies = [
    {
        title: "foo bar",
        genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
        numberInStock: 8,
        dailyRentalRate: 3
    },
    {
        title: "Terminator",
        genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
        numberInStock: 6,
        dailyRentalRate: 2.5,
    },
    {
        title: "Die Hard",
        genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
        numberInStock: 5,
        dailyRentalRate: 2
    },
    {
        title: "Get Out",
        genre: { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" },
        numberInStock: 8,
        dailyRentalRate: 3.5
    }
];

let path = "title";

const getNested = (obj, path) => path.split('.').reduce((a, prop) => a[prop], obj);
const myCompare = (a, b) => {
  const aVal = getNested(a, path);
  return typeof aVal === 'string'
  ? aVal.localeCompare(getNested(b, path))
  : aVal - getNested(b, path);
};

// Sort by title:
movies.sort(myCompare);
console.log(movies);

// Sort by genre.name:
path = 'genre.name';
movies.sort(myCompare);
console.log(movies);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
0

Probably, you can write a helper function to get nested values, something like this:

function getNestedValue(fieldName, item) {
    if (fieldName.includes('.')) {
        const keyArr = fieldName.split('.');
        let result = item;
        if (keyArr.length > 1) {
            for (const key of keyArr) {
                result = result[key];
            }

            return result;
        }
    }

    return item[fieldName];
}

myCompare = (a, b) => {
    const { path } = this.state.sortColumn;
    const valueA = getNestedValue(path, a);
    const valueB = getNestedValue(path, b)

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

And compare the given result.

Alexandr
  • 95
  • 8