简体   繁体   中英

How to group and sort object array based on string index

I want to group & re-arrange my flat array into an array of objects where each object contains its direct descendent in its children's properties.

My input Data -:

const data = [
  { lineNo: '1' },
  { lineNo: '1-1' },
  { lineNo: '1-2' },
  { lineNo: '1-3' },
  { lineNo: '1-4' },
  { lineNo: '1-11' },
  { lineNo: '1-2-1' },
  { lineNo: '1-2-3' },
  { lineNo: '1-2-2' },
  { lineNo: '1-2-4' },
  { lineNo: '1-3-1' },
  { lineNo: '1-3-2' },
  { lineNo: '1-3-3' },
  { lineNo: '1-11-1' },
  { lineNo: '1-11-2' },
  { lineNo: '1-11-3' },
  { lineNo: '2' },
  { lineNo: '2-1' },
  { lineNo: '2-2' },
  { lineNo: '2-3' },
  { lineNo: '2-2-1' },
  { lineNo: '2-2-2' },
];

The output I want -:

const newData = [
  {
    lineNo: '1',
    children: [{ lineNo: '1-1' }, { lineNo: '1-2' }, { lineNo: '1-3' }, { lineNo: '1-4' }, { lineNo: '1-11' }],
  },
  {
    lineNo: '1-2',
    children: [{ lineNo: '1-2-1' }, { lineNo: '1-2-2' }, { lineNo: '1-2-3' }, { lineNo: '1-2-4' }],
  },
  {
    lineNo: '1-3',
    children: [{ lineNo: '1-3-1' }, { lineNo: '1-3-2' }, { lineNo: '1-3-3' }],
  },
  {
    lineNo: '1-11',
    children: [{ lineNo: '1-11-1' }, { lineNo: '1-11-2' }, { lineNo: '1-11-3' }],
  },
  {
    lineNo: '2',
    children: [{ lineNo: '2-1' }, { lineNo: '2-2' }, { lineNo: '2-3' }],
  },
  {
    lineNo: '2-2',
    children: [{ lineNo: '2-2-1' }, { lineNo: '2-2-2' }],
  },
];

Note: The generated data must have to follow the numerical order mentioned in the output.

What i have done so far -:

const data = [
  { lineNo: '1' },
  { lineNo: '1-1' },
  { lineNo: '1-2' },
  { lineNo: '1-3' },
  { lineNo: '1-4' },
  { lineNo: '1-11' },
  { lineNo: '1-11-1' },
  { lineNo: '1-11-2' },
  { lineNo: '1-11-3' },
  { lineNo: '1-2-1' },
  { lineNo: '1-2-3' },
  { lineNo: '1-2-2' },
  { lineNo: '1-2-4' },
  { lineNo: '1-2-1-1' },
  { lineNo: '1-2-1-2' },
  { lineNo: '1-2-1-3' },
  { lineNo: '1-3-1' },
  { lineNo: '1-3-2' },
  { lineNo: '1-3-3' },
];

function createTree(data: any) {
  const tree: any[] = [];
  data.reduce(
    (r: any, o: any) => {
      o.lineNo
        .split('-')
        .map((_: any, i: any, a: any) => a.slice(0, i + 1).join('-'))
        .reduce((q: any, lineNo: any, i: any, { length }: any) => {
          let temp = (q.children = q.children || []).find((p: any) => p.lineNo === lineNo);
          if (!temp) {
            q.children.push((temp = { lineNo }));
          }
          if (i + 1 === length) {
            Object.assign(temp, o);
          }
          return temp;
        }, r);
      return r;
    },
    { children: tree }
  );

  return tree;
}

const createFlat = (data: any) => {
  const flat: any[] = [];
  const flatData = (data: any) => {
    data.forEach((item: any) => {
      if (item.children) {
        flat.push(item);
        flatData(item.children);
      }
    });
  };
  flatData(data);
  return JSON.parse(JSON.stringify(flat));
};

const getCleanData = (data: any) => {
  const cleanData: any[] = [];
  function delChildren(obj: any) {
    if (obj.children) {
      obj.children.forEach((item: any) => {
        if (item.children) {
          delete item.children;
        }
      });
    }
    return obj;
  }

  data.forEach(function (item: any) {
    cleanData.push(delChildren(item));
  });

  return cleanData;
};

const treeData = createTree(data);
const flatData = createFlat(treeData);
const cleanData = getCleanData(flatData);
console.log(JSON.stringify(cleanData, null, 2));

I am looking for a bit simple and clean approach.

Thank You!

You could first sort the data using a "natural" sort. JavaScript localeCompare has an option for that.

Then create a Map keyed by lineNo and with as corresponding values the objects with the same lineNo property and an empty children array property.

Then iterate the data again to populate those children arrays.

Extract the Map values and remove those objects that have an empty children array, unless they are top-level nodes (a boundary case).

Here is an implementation:

 function createTree(data) { // Apply natural ordering data = [...data].sort((a, b) => a.lineNo.localeCompare(b.lineNo, "en", { numeric: true }) ); // Create key/value pairs for all lineNo in a Map let map = new Map(data.map(({lineNo}) => [lineNo, { lineNo, children: [] }])); // Populate the children arrays for (let {lineNo} of data) { map.get(lineNo.replace(/-?\d+$/, ""))?.children?.push({lineNo}); } // Exclude the nodes that have no children, except if they are top-level return [...map.values()].filter(({lineNo, children}) =>.lineNo.includes("-") || children;length ): } // Demo const data = [{ lineNo, '1-1' }:{ lineNo, '1-2' }:{ lineNo, '1-3-2' }:{ lineNo, '1-11-2' }:{ lineNo, '1-3' }:{ lineNo, '1-11' }:{ lineNo, '1-2-3' }:{ lineNo, '1-2-2' }:{ lineNo, '1' }:{ lineNo, '1-2-1' }:{ lineNo, '1-2-4' }:{ lineNo, '1-11-1' }:{ lineNo, '1-3-1' }:{ lineNo, '1-4' }:{ lineNo, '1-3-3' }:{ lineNo, '2-1' }:{ lineNo, '2-2' }:{ lineNo, '2-2-1' }:{ lineNo, '1-11-3' }:{ lineNo, '2' }:{ lineNo, '2-3' }:{ lineNo, '2-2-2' };]. console;log(createTree(data));

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