3

I've have a complex data structure with multiple nested arrays in place.

Below is the current structure

var contentData = {
  data: {
    content: [
      {
        type: "column",
        sections: [
          {
            sub: [
              {
                type: "heading-1",
                text: "Heading Text"
              }
            ]
          }
        ]
      },
      {
        type: "acc-item",
        sections: [
          {
            sub: [
              {
                type: "heading-1",
                text: "Heading Text"
              },
              {
                type: "ordered-item",
                text: "Item 1"
              },
              {
                type: "unordered-item",
                text: "Item 2"
              }
            ]
          }
        ]
      },
      {
        type: "acc-item",
        sections: [
          {
            sub: [
              {
                type: "heading-1",
                text: "Heading Text 2"
              }
            ]
          }
        ]
      }
    ]
  }
}

So What I wanted is,

  1. I wanted to group all the ordered-item & unordered-item into a new object like {type: 'list', items:[all list items]}.

  2. I need to extract all items which are inside sub and push it to new object embedded and it should placed in the root level like below,

    {type:"acc-item",embedded:[{type:"heading-1",text:"Heading Text 2"}]};

So What I've done so far,

I can able to group acc-item, but not the ordered-item & unordered-item.

So my final expected result should like this,

[{
  "type": "column",
  "embedded": [
    {
      "type": "heading-1",
      "text": "Heading Text"
    }
  ]
},
{
  "type": "acc-group",
  "items": [
    {
      "type": "acc-item",
      "embedded": [
        {
          "type": "heading-1",
          "text": "Heading Text"
        },
        {
          "type": "list",
          "items": [
            {
              "type": "ordered-item",
              "text": "Item 1"
            },
            {
              "type": "unordered-item",
              "text": "Item 2" 
            }
          ]
        }
      ]
    },
    {
      "type": "acc-item",
      "embedded": [
        {
          "type": "heading-1",
          "text": "Heading Text 2"
        }
      ]
    }
  ]
}]

Below is my code,

var group,contentData={data:{content:[{type:"column",sections:[{sub:[{type:"heading-1",text:"Heading Text"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text"},{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text 2"}]}]}]}},types=[["list",["ordered-item","unordered-item"]],["accordion",["acc-item"]]];

var result = contentData.data.content.reduce((r, o) => {
  var type = (types.find(({ 1: values }) => values.indexOf(o.type) > -1)|| {})[0];
  if (!type) {
    r.push(o);
    group = undefined;
    return r;
  }
  if (!group || group.type !== type) {
    group = { type, items: [] };
    r.push(group);
  }
  group.items.push(o);
  return r;
}, []);

document.body.innerHTML = '<pre>' + JSON.stringify(result, null, '  ') + '</pre>';

0

2 Answers 2

1

You could store the last items array as well as the last embedded array and use them until a column type is found.

var contentData = { data: { content: [{ type: "column", sections: [{ sub: [{ type: "heading-1", text: "Heading Text" }] }] }, { type: "acc-item", sections: [{ sub: [{ type: "heading-1", text: "Heading Text" }, { type: "ordered-item", text: "Item 1" }, { type: "unordered-item", text: "Item 2" }] }] }, { type: "acc-item", sections: [{ sub: [{ type: "heading-1", text: "Heading Text 2" }] }] }] } },
    list = ["ordered-item", "unordered-item"],
    lastItems, lastEmbedded,
    result = contentData.data.content.reduce((r, { type, sections }) => {
        if (type === 'column') {
            r.push({ type, embedded: sections.reduce((q, { sub }) => q.concat(sub), []) });
            lastItems = undefined;
            lastEmbedded = undefined;
            return r;
        }
        if (!lastItems) r.push({ type: "acc-group", items: lastItems = [] });
        lastItems.push(...sections.map(({ sub }) => ({
            type,
            embedded: sub.reduce((q, o) => {
                if (list.includes(o.type)) {
                    if (!lastEmbedded) q.push({ type: 'list', items: lastEmbedded = [] });
                    lastEmbedded.push(o);
                } else {
                    q.push(o);
                    lastEmbedded = undefined;
                }
                return q;
            }, [])
        })));
        return r;
    }, []);

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

Sign up to request clarification or add additional context in comments.

4 Comments

one issue, "ordered-item", "unordered-item" is not grouping inside columns
Can you use this var contentData={data:{content:[{type:"column",sections:[{sub:[{type:"heading-1",text:"Heading Text"},{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text"},{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]},{type:"acc-item",sections:[{sub:[{type:"heading-1",text:"Heading Text 2"}]}]},{type:"column",sections:[{sub:[{type:"heading-1",text:"Heading Text"},{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]}]}};
how shold look this as result?
list is not grouping inside column, so expected result [{type:"column",embedded:[{type:"heading-1",text:"Heading Text"},{type:"list",items:[{type:"ordered-item",text:"Item 1"},{type:"unordered-item",text:"Item 2"}]}]}];
0

The Array.prototype and Object.prototype methods are perfect for this kind of thing.

And you're right that this is some complicated kind of logic.

I would suggest that you definitely need some unit tests for this, and try break in to separate pieces.

Here's how I'm thinking I'd do it.

1. Group By the type to create your groups..

I'm actually creating a more generic solution that you've asked for here. That is, I'm not just grouping the 'acc-item', but everything.

I did a quick search for 'array group by javascript' and it gives us this answer which suggests using Array.reduce, so let's do that.

    const groupedData = contentData.data.content.reduce((acc, cur) => {
        //Check if this indexed array already exists, if not create it. 
        const currentArray = (acc[`${cur.type}-group`] && acc[`${cur.type}-group`].items) || [];

        return {
          ...acc,
          [`${cur.type}-group`]: {
              type: `${cur.type}-group`, 
              items: [...currentArray, cur]
          }
        }
    }, {}); 

2. Now for each of those items, we need to look at their subs, and group just the list items.

To do this, we basically want to find all the `item -> sections -> sub -> types and filter them into two arrays. A quick google on how to create two arrays using a filter gives me this answer.

First though, we need to flatten that sections-> subs thing, so lets just do that.

function flattenSectionsAndSubs(item) {
    return {
        type: item.type, 
        subs: item.sections.reduce((acc, cur) => ([...acc, ...cur.sub]), [])
    }; 
}

And I'll just copy paste that partition function in:

function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
  }, [[], []]);
}


    const listTypes = ['ordered-item', 'unordered-item']; 
    function createEmbeddedFromItem(item) {
        const [lists, nonLists] = partition(item.subs, (v) => listTypes.includes(v.type); 

      return {
         type: item.type, 
         embedded: [
             ...nonLists, 
             {
                type: "list", 
                items: lists
             }
         ]
      }
    }

Putting this all together and we get.

const contentData = {
  data: {
    content: [{
        type: "column",
        sections: [{
          sub: [{
            type: "heading-1",
            text: "Heading Text"
          }]
        }]
      },
      {
        type: "acc-item",
        sections: [{
          sub: [{
              type: "heading-1",
              text: "Heading Text"
            },
            {
              type: "ordered-item",
              text: "Item 1"
            },
            {
              type: "unordered-item",
              text: "Item 2"
            }
          ]
        }]
      },
      {
        type: "acc-item",
        sections: [{
          sub: [{
            type: "heading-1",
            text: "Heading Text 2"
          }]
        }]
      }
    ]
  }
}


function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [
      [...pass, elem], fail
    ] : [pass, [...fail, elem]];
  }, [
    [],
    []
  ]);
}


function flattenSectionsAndSubs(item) {
  return {
    type: item.type,
    subs: item.sections.reduce((acc, cur) => ([...acc, ...cur.sub]), [])
  };
}

const listTypes = ['ordered-item', 'unordered-item'];

function createEmbeddedFromItem(item) {
  const [lists, nonLists] = partition(item.subs, (v) => listTypes.includes(v.type));

    return {
      type: item.type,
      embedded: [
        ...nonLists,
        {
          type: "list",
          items: lists
        }
      ]
    }
  }


  const groupedData = contentData.data.content.reduce((acc, cur) => {
    //Check if this indexed array already exists, if not create it. 
    const currentArray = (acc[`${cur.type}-group`] && acc[`${cur.type}-group`].items) || [];

    const flattenedItem = flattenSectionsAndSubs(cur);
    const embeddedItem = createEmbeddedFromItem(flattenedItem);
    return {
      ...acc,
      [`${cur.type}-group`]: {
          type: `${cur.type}-group`, 
          items: [...currentArray, embeddedItem]
      }
    }
  }, {});

  console.log(groupedData);

Now this doesn't exactly match what you've asked for - but it should probably work.

You can add your own bits into only add a list item, if the array isn't empty, and to stop the column from being in its own group.

The thing is - tbh it seems like a little bit of a red flag that you would create an array of items that don't having matching structures, which is why I've done it this way.

5 Comments

in flattenSectionsAndSubs .flat dosent work in IE
See this answer here: stackoverflow.com/questions/38696119/… You might want to use a polyfill, or use babel to compile your production code, or change the syntax slightly.
I've copied the code here, so that it's compiled with babel - see that it works there: codepen.io/dwjohnston/pen/KLBwza?editors=1111
I could see a empty list getting added in the result
@user007 yes. See my final comments. You can edit the code yourself to get this to do exactly what you want. I've shown you the general process.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.