2

I have a persons list and I sort it by the column in sortColumn.

const persons = [{
    name: "alireza",
    family: "seif",
    other: {
      age: 28,
      rate: 30
    }
  },
  {
    name: "sara",
    family: "niki",
    other: {
      age: 15,
      rate: 15
    }
  },
  {
    name: "fateme",
    family: "azizy",
    other: {
      age: 27,
      rate: 35
    }
  }
];
const sortColumn = {
  path: "name",
  order: "asc"
};
persons.sort((person1, person2) =>
  person1[sortColumn.path] > person2[sortColumn.path] ?
  sortColumn.order === "asc" ?
  1 :
  -1 :
  person2[sortColumn.path] > person1[sortColumn.path] ?
  sortColumn.order === "asc" ?
  -1 :
  1 :
  0
);
console.log(persons);

If sortColumn.path is "name" and order is "asc" (or "desc"), the sort function works correctly. But how can I sort by "other.age"?

Thanks.

Alessio Cantarella
  • 5,077
  • 3
  • 27
  • 34
A.R.SEIF
  • 865
  • 1
  • 7
  • 25

3 Answers3

2

If you want to do sort by that deep property age, you can't use sortColumn exactly as you have... Instead, one option is to modify it by making it an array of properties, like so:

sortColumn = { path:["other","age"], order:"asc" }

This way, you'd also have to modify the sort function - as seen in the example below:

const persons = [
  {name:"alireza",family:"seif",other:{age:28,rate:30}},
  {name:"fateme",family:"azizy",other:{age:27,rate:35}},
  {name:"sara",family:"niki",other:{age:15,rate:15}}
]

const sortColumn = { path:["other","age"], order:"asc" }

persons.sort((person1, person2) =>
  person1[sortColumn.path[0]][sortColumn.path[1]] > person2[sortColumn.path[0]][sortColumn.path[1]] 
    ? sortColumn.order === "asc"
      ? 1
      : -1
    : person2[sortColumn.path[0]][sortColumn.path[1]]  > person1[sortColumn.path[0]][sortColumn.path[1]] 
    ? sortColumn.order === "asc"
      ? -1
      : 1
    : 0
);

console.log(persons)

However, this approach doesn't work to sort by "name" since this sort function sorts your data by some piece of data that is two-layers deep (inside "other", then "age"). Here's a modification you can make to the sort function which lets you sort by any properties, any number of layers deep into your data:

const persons = [
  {name:"alireza",family:"seif",other:{age:28,rate:30}},
  {name:"fateme",family:"azizy",other:{age:27,rate:35}},
  {name:"sara",family:"niki",other:{age:15,rate:15}}
]

const sortColumn = { path:["name"], order:"asc" }

persons.sort((person1, person2) => {
  // get array of paths
  const sortPaths = sortColumn.path;

  // get values to sort by
  const val1 = sortPaths.reduce((acc, path) => {
    if (acc[path]) return acc[path]
    else alert(`can't find prop ${path} in person1`);
  }, person1)
  const val2 = sortPaths.reduce((acc, path) => {
    if (acc[path]) return acc[path]
    else alert(`can't find prop ${path} in person2`);
  }, person2)

  return val1 > val2 
    ? sortColumn.order === "asc"
      ? 1
      : -1
    : val2  > val1 
    ? sortColumn.order === "asc"
      ? -1
      : 1
    : 0
});

console.log(persons)
https://stackoverflow.com/questions/59792589/sort-object-in-object-by-javascript/59792918#

As you can see in this second snippet, you can now search by a shallow property "name" by using path: ["name"], but if you want to sort by a deep value, just add both properties to the path array like this: path: ["other", "age"]

Hope this helps!

Blundering Philosopher
  • 6,245
  • 2
  • 43
  • 59
2

The main thing to do is to make your code expect 'path' be a function that selects a property's value, instead of a string pointing to a property name. That way it's much more flexible.

const sortColumn = {
    path: p => p.other.age,
    order: 'desc'
};

However, if 'persons' is not the only object you'd like to do this with, you can further abstract such a function for general use with any array, such as this:

function sorter (array, path, order) {

  array.sort((a,b) => {

    let result = 
      path(a) > path(b) ? 1
      : path(a) < path(b) ? -1
      : 0;

    if (order === "desc")
      result = -result;

    return result;

  });

}

Use it like this:

sorter(persons, p => p.other.age, 'desc');

Expand and run the snippet below to see it in action:

function sorter (array, path, order) {

  array.sort((a,b) => {

    let result = 
      path(a) > path(b) ? 1
      : path(a) < path(b) ? -1
      : 0;

    if (order === "desc")
      result = -result;

    return result;

  });
  
}

const persons = [
      {name: "alireza", family: "seif", other: {age: 28, rate: 30}},
      {name: "sara", family: "niki", other: {age: 15, rate: 15}},
      {name: "fateme", family: "azizy", other: {age: 27, rate: 35}}
];

// convenience function
let sortAndLog = (array, path, order) => {
    sorter(array, path, order);
    console.log(array.map(path));
}
sortAndLog(persons, p => p.name, "asc");
sortAndLog(persons, p => p.name, "desc");
sortAndLog(persons, p => p.other.age, "asc");
sortAndLog(persons, p => p.other.age, "desc");
pwilcox
  • 5,542
  • 1
  • 19
  • 31
2

You could take a functin which return a sort function, depending on the sort order.

This sorting function uses another function for getting a value from an object by reducing the splitted path to the value.

const
    sortBy = ({ order = 'asc', path }) => order === 'asc'
        ? (a, b) => ((a, b) => a > b || -(a < b))(getValue(a, path), getValue(b, path))
        : (a, b) => ((a, b) => b > a || -(b < a))(getValue(a, path), getValue(b, path)),
    getValue = (object, keys) => keys.split('.').reduce((o, k) => o[k], object),
    persons = [{ name: "sara", family: "niki", other: { age: 15, rate: 15 } }, { name: "alireza", family: "seif", other: { age: 28, rate: 30 } }, { name: "fateme", family: "azizy", other: { age: 27, rate: 35 } }];

console.log(persons.sort(sortBy({ path: "name", order: "asc" })));
console.log(persons.sort(sortBy({ path: "other.rate", order: "desc" })));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392