Home > front end >  What are transforming approaches that collect array items from a nested data-structure into a single
What are transforming approaches that collect array items from a nested data-structure into a single

Time:09-07

Given is a nested data-structure which I want to get transformed into a single flat array of non nested array-items.

The data and the code I came up with so far are as follows ...

const data = [{
  heading: [{
    name: "Heading 01",
    Items: [{
      name: "Item 01",
      layers: [{
        name: "layer01",
        id: 4,
        parent: 3,
        droppable: false,
      }, {
        name: "layer02",
        id: 5,
        parent: 3,
        droppable: false,
      }],
      id: 3,
      parent: 2,
      droppable: true,
    }],
    id: 2,
    parent: 1,
    droppable: true,
  }],
  id: 1,
  parent: 0,
  droppable: true,
}];

const flatArray = [];
const flatObject = {};
  
for (let index = 0; index < data.length; index  ) {
  for (const prop in data[index]) {

    const value = data[index][prop];

    if (Array.isArray(value)) {
      for (let i = 0; i < value.length; i  ) {
        for (const inProp in value[i]) {
          flatObject[inProp] = value[i][inProp];
        }
      }
    } else {
      flatObject[prop] = value;
    }
  }
  flatArray.push(flatObject);
}

console.log(flatArray)
.as-console-wrapper { min-height: 100%!important; top: 0; }

... and the result is expected to be equal to the next provided data structure ...

[{
  id: 1,
  parent: 0,
  droppable: true,
}, {
  name: "Heading 01",
  id: 2,
  parent: 1,
  droppable: true,
}, {
  name: "Item 01",
  id: 3,
  parent: 2,
  droppable: true,
}, {
  name: "layer01",
  id: 4,
  parent: 3,
  droppable: false,
}, {
  name: "layer02",
  id: 5,
  parent: 3,
  droppable: false,
}]

CodePudding user response:

One could come up with a recursive approach which targets specific array items by any wanted array's key/identifier which are all going to be used by a destructuring assignment with default values and rest property.

The advantage of the destructuring is the separation and the direct access of all relevant data, the possible to be targeted array(s) and the rest of the currently processed data item.

function collectSpecificArrayItemsRecursively(data) {
  const result = [];

  if (Array.isArray(data)) {

    result
      .push(
        ...data
          .flatMap(collectSpecificArrayItemsRecursively)
      );
  } else {

    const {
      // destructuring into specific
      // arrays and the data's rest.
      heading = [], Items = [], layers = [], ...rest
    } = data;

    result
      .push(
        rest,
        ...heading
          .concat(Items)
          .concat(layers)
          .flatMap(collectSpecificArrayItemsRecursively),
      );    
  }
  return result;
}
const data = [{
  heading: [{
    name: "Heading 01",
    Items: [{
      name: "Item 01",
      layers: [{
        name: "layer01",
        id: 4,
        parent: 3,
        droppable: false,
      }, {
        name: "layer02",
        id: 5,
        parent: 3,
        droppable: false,
      }],
      id: 3,
      parent: 2,
      droppable: true,
    }],
    id: 2,
    parent: 1,
    droppable: true,
  }],
  id: 1,
  parent: 0,
  droppable: true,
}];

console.log(
  collectSpecificArrayItemsRecursively(data)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

Your data is not really in a recursive structure. So, while we can write a function that will do the transform, it's more complex and more fragile than one would hope.

Here is one possibility:

const flatten = (data) =>
  data .flatMap (({heading = [], Items = [], layers = [], ...rest}) => [
    rest, 
    ...flatten (heading), 
    ...flatten (Items), 
    ...flatten (layers)
  ])

const data = [{heading: [{name: "Heading 01", Items: [{name: "Item 01", layers: [{name: "layer01", id: 4, parent: 3, droppable: !1}, {name: "layer02", id: 5, parent: 3, droppable: !1}], id: 3, parent: 2, droppable: !0}], id: 2, parent: 1, droppable: !0}], id: 1, parent: 0, droppable: !0}]

console .log (flatten (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

The fragility I mention has to do with the fact that we specifically have to hard-code the levels here, heading, Items, layers. That they are indiscriminately capitalized and pluralized is a minor problem. The major problem is that they are all different. We are searching every level for each possible name for its children. And if we reused a name for a different purpose at one level (say the top level also had layers: 7, this would fall apart.

However, if you're in charge of that format, you can simplify it by combining these under the name, children. That will lead to simpler, more robust code, and more consistent data structures.

It would mean making your input look like this:

const data = [{
  children: [{          // the node formerly known as `heading`
    name: "Heading 01",
    children: [{        // the node formerly known as `Items`
      name: "Item 01",
      children: [{      // the node formerly known as `layers`
        name: "layer01",
        id: 4,
        parent: 3,
        droppable: false,
      }, {
        name: "layer02",
        id: 5,
        parent: 3,
        droppable: false,
      }],
      id: 3,
      parent: 2,
      droppable: true,
    }],
    id: 2,
    parent: 1,
    droppable: true,
  }],
  id: 1,
  parent: 0,
  droppable: true,
}]

The name children could be replaced with anything you like, as long as it's consistent.

With that, we could simply do this:

const flatten = (data) =>
  data .flatMap (({children = [], ...rest}) => [rest, ...flatten (children)])

const data = [{children: [{name: "Heading 01", children: [{name: "Item 01", children: [{name: "layer01", id: 4, parent: 3, droppable: !1}, {name: "layer02", id: 5, parent: 3, droppable: !1}], id: 3, parent: 2, droppable: !0}], id: 2, parent: 1, droppable: !0}], id: 1, parent: 0, droppable: !0}]

console .log (flatten (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

Here we simply separate out children from the rest of the node, return the rest as a new object followed by the result of recurring on children. This is an elegant little function, easy to write and easy to understand. But it requires a consistent data format to get it to work.

CodePudding user response:

Recursive approach:

const data = [{"id": 1,"parent": 0,"droppable": true,"heading": [{"name": "Heading 01","Items": [{"name": "Item 01","layers": [{"name": "layer01","id": 4,"parent": 3,"droppable": false},{"name": "layer02","id": 5,"parent": 3,"droppable": false}],"id": 3,"parent": 2,"droppable": true}],"id": 2,"parent": 1,"droppable": true}]}];
  
const iter = (data) => data.reduce((acc, obj) => {
    const entries = Object.entries(obj);
    const arrays = entries.filter(([, value]) => Array.isArray(value)); 
    const objWithoutArrays = entries
      .reduce((acc, [key, value]) => Array.isArray(value) 
        ? acc 
        : { ...acc, [key]: value }
      , {});
      
    acc.push(objWithoutArrays);
    acc.push(...arrays.flatMap(([, value]) => iter(value)));
    
    return acc;
}, []);

console.log(iter(data));
.as-console-wrapper { max-height: 100% !important; top: 0 }

CodePudding user response:

here is my solution.

const c = (item: any) => "name" in item ? [{
  name: item.name,
  id: item.id,
  parent: item.parent,
  droppable: item.droppable
}] : []

function flat(item: any) {
  if ("heading" in item) {
    return c(item).concat(item.heading.flatMap(flat))
  }

  if ("Items" in item) {
    return c(item).concat(item.Items.flatMap(flat))
  }

  if ("layers" in item) {
    return c(item).concat(item.layers.flatMap(flat))
  }

  return c(item)
}

console.log(data.flatMap(flat))
  • Related