简体   繁体   中英

Reorder array of objects that contain parent/child relationships with lodash or vanilla js

I have an array of objects like:

[
    {
        id: 8,
        name: 'Shirts',
        slug: 'shirts',
        parent_id: null
    },
    {
        id: 9,
        name: 'Pants',
        slug: 'pants',
        parent_id: null
    },
    {
        id: 10,
        name: 'Vintage Prints',
        slug: 'vintage-prints',
        parent_id: 8
    },
    {
        id: 11,
        name: 'Cotton Tee',
        slug: 'cotton-tee',
        parent_id: 8
    },
    {
        id: 12,
        name: 'Business Khakis',
        slug: 'business-khakis',
        parent_id: 9
    }
]

What I need is:

[
    {
        id: 9,
        name: 'Pants',
        slug: 'pants',
        parent_id: null
    },
    {
        id: 12,
        name: 'Business Khakis',
        slug: 'business-khakis',
        parent_id: 9
    },
    {
        id: 8,
        name: 'Shirts',
        slug: 'shirts',
        parent_id: null
    },
    {
        id: 11,
        name: 'Cotton Tee',
        slug: 'cotton-tee',
        parent_id: 8
    },
    {
        id: 10,
        name: 'Vintage Prints',
        slug: 'vintage-prints',
        parent_id: 8
    }
]

WHAT I'VE TRIED: This looks like it should work:

_.orderBy(categories, ['parent_id', 'name'], ['asc', 'asc']);

But I wonder if the nulls in parent_id are messing with it.

EDIT: Inner and outer results should also be sorted alphabetically. So Pants before shirts in the outer level and Cotton Tee before Vintage Prints in the child tier. Keep in mind that this can be infinite layers deep where Cotton Tee could be the parent and so on.

It would also be great if each sorted object could receive an index or level so that you knew how many levels it was nested.

A single sort does not work, because of the parent children relation, which is not considered while sorting the data.

This solution works in three parts:

  1. Sorts data by alphabet, because the following tree is build in insertion order.

  2. Builds a tree with the given relationship.

  3. Traverses the tree and gets the sorted flat data back.

 var data = [{ id: 8, name: 'Shirts', slug: 'shirts', parent_id: null }, { id: 9, name: 'Pants', slug: 'pants', parent_id: null }, { id: 10, name: 'Vintage Prints', slug: 'vintage-prints', parent_id: 8 }, { id: 11, name: 'Cotton Tee', slug: 'cotton-tee', parent_id: 8 }, { id: 12, name: 'Business Khakis', slug: 'business-khakis', parent_id: 9 }].sort(function (a, b) { return a.name.localeCompare(b.name); }), tree = function (data, root) { var r = [], o = {}; data.forEach(function (a) { o[a.id] = { data: a, children: o[a.id] && o[a.id].children }; if (a.parent_id === root) { r.push(o[a.id]); } else { o[a.parent_id] = o[a.parent_id] || {}; o[a.parent_id].children = o[a.parent_id].children || []; o[a.parent_id].children.push(o[a.id]); } }); return r; }(data, null), sorted = tree.reduce(function traverse(r, a) { return r.concat(a.data, (a.children || []).reduce(traverse, [])); }, []) console.log(sorted); console.log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

Extended version with level:

 var data = [{ id: 8, name: 'Shirts', slug: 'shirts', parent_id: null }, { id: 9, name: 'Pants', slug: 'pants', parent_id: null }, { id: 10, name: 'Vintage Prints', slug: 'vintage-prints', parent_id: 8 }, { id: 11, name: 'Cotton Tee', slug: 'cotton-tee', parent_id: 8 }, { id: 12, name: 'Business Khakis', slug: 'business-khakis', parent_id: 9 }].sort(function (a, b) { return a.name.localeCompare(b.name); }), tree = function (data, root) { var r = [], o = {}; data.forEach(function (a) { o[a.id] = { data: a, children: o[a.id] && o[a.id].children }; if (a.parent_id === root) { r.push(o[a.id]); } else { o[a.parent_id] = o[a.parent_id] || {}; o[a.parent_id].children = o[a.parent_id].children || []; o[a.parent_id].children.push(o[a.id]); } }); return r; }(data, null), sorted = tree.reduce(function traverse(level) { return function (r, a) { a.data.level = level return r.concat(a.data, (a.children || []).reduce(traverse(level + 1), [])); }; }(0), []); console.log(sorted);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

This is my solution

It handles multiple nested elements.

I got a solution I use lodash for array equality but you can implement it in pure JS also

const sortByLevel = (
  array: ({ id: string, parent_id: string, level: number })[],
  i: number = 0,
): ({ id: string, parent_id: string, level: number })[] => {
  const sortedArray = array
    .map((c) => ({ ...c, level: array.findIndex((a) => a.id === c.parent_id) }))
    .sort((a, b) => a.level - b.level);

  if (
    _.isEqual(
      sortedArray.map((c) => c.id),
      array.map((c) => c.id),
    )
  )
    return sortedArray;

  return sortByLevel(sortedArray, i + 1);
};

const sortByParent = (array: { id: string, parent_id: string }[]): { id: string, parent_id: string }[] => {
  return sortByLevel(
    array
      .map((c) => ({ ...c, level: array.findIndex((a) => a.id === c.parent_id) }))
      .sort((a, b) => a.level - b.level),
    1,
  ).map((c) => {
    const { level, ...card } = c;
    return card;
  });
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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