skip to Main Content

I have this starting array:

const data = [
  {
    roles: [
      {
        name: "one",
      },
      {
        name: "two",
      },
    ],
    name: "Alfa",
  },
  {
    roles: [
      {
        name: "three",
      },
    ],
    name: "Bravo",
  },
  {
    name: "Charlie",
  },
];

I need to create an object for every name. If a name has more than one role, one item for name.

So, with the example, I need this array:

[
  { name: "Alfa", role: "one" },
  { name: "Alfa", role: "two" },
  { name: "Bravo", role: "three" },
  { name: "Charlie" }
];

This is the function that I wrote:

const cleanRoles = (data) => {
  const roles = data.map((person) => {
    if (Array.isArray(person.roles) && person.roles.length > 0) {
      return person.roles.map((role) => {
        return {
          name: person.name,
          role: role.name,
        };
      });
    }
    return {
      name: person.name,
    };
  });
  return roles;
};

But I get

[
  [
    { name: "Alfa", role: "one" },
    { name: "Alfa", role: "two" },
  ],
  [{ name: "Bravo", role: "three" }],
  { name: "Charlie" },
];

How can I refactor my function to get what I need?

5

Answers


  1. flatMap does what map does, but expects arrays as return values and concatenates those together into 1 large array as output:

    const cleanRoles = (data) =>
      data.flatMap((person) => {
        if (Array.isArray(person.roles) && person.roles.length > 0) {
          return person.roles.map((role) => {
            return {
              name: person.name,
              role: role.name,
            };
          });
        }
        return {
          name: person.name,
        };
      });
    console.log(cleanRoles(data));
    <script>
      const data = [{
          roles: [{
              name: "one",
            },
            {
              name: "two",
            },
          ],
          name: "Alfa",
        },
        {
          roles: [{
            name: "three",
          }, ],
          name: "Bravo",
        },
        {
          name: "Charlie",
        },
      ];
    </script>
    Login or Signup to reply.
  2. Shorter versions

    const cleanRoles = data => data
      .flatMap(({name,roles}) => roles?.length    ? roles
        .map(role => ({ name, role: role.name })) : ({ name }));
    
    console.log(cleanRoles(data));
    
    
    // or the faster (concat seems to be expensive too)
    
    const reduceRoles = data => data.reduce((acc, { name, roles }) => (acc.concat(roles?.length ?  roles.map(role => ({ name, role: role.name })) : { name })), []);
    
    console.log(reduceRoles(data));
    <script>
      const data = [{
          roles: [{
              name: "one",
            },
            {
              name: "two",
            },
          ],
          name: "Alfa",
        },
        {
          roles: [{
            name: "three",
          }, ],
          name: "Bravo",
        },
        {
          name: "Charlie",
        },
      ];
    </script>
    <script benchmark="1000000">
    
          const data = [{
              roles: [{
                  name: "one",
                },
                {
                  name: "two",
                },
              ],
              name: "Alfa",
            },
            {
              roles: [{
                name: "three",
              },],
              name: "Bravo",
            },
            {
              name: "Charlie",
            },
          ];
          
    
    // @benchmark mplungjan1
    data
      .flatMap(({name,roles}) => roles?.length    ? roles
        .map(role => ({ name, role: role.name })) : ({ name }));
        
    // @benchmark mplungjan2    
    
    data.reduce((acc, { name, roles }) => (acc.concat(roles?.length ?  roles.map(role => ({ name, role: role.name })) : { name })), []);
    
    
    // @benchmark Mister Jojo      
    data.reduce( (acc, {roles,name},i ) =>
      {
      if (Array.isArray(roles) && roles.length)
    roles.forEach(({name:role}) => acc.push({name,role}));
      else
    acc.push({name});
      return acc;
      },[]);    
          
    // @benchmark Robin De Schepper      
    data.flatMap((person) => {
        if (Array.isArray(person.roles) && person.roles.length > 0) {
          return person.roles.map((role) => {
            return {
              name: person.name,
              role: role.name,
            };
          });
        }
        return {
          name: person.name,
        };
      });      
          
    
    
    // @benchmark Alexander
    data.reduce((r, {name, roles}) => 
          (roles?.length ? roles.forEach(({name: role}) => r.push({name, role})) : r.push({name}), r), []);
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  3. Reduce the data with mapping roles to new array items. Avoid .flatMap() since it’s slower than reducing:

    const result = data.reduce((r, {name, roles}) => 
      (roles?.length ? roles.forEach(({name: role}) => r.push({name, role})) : r.push({name}), r), []);
    
    console.log(result);
    <script>
      const data = [{
          roles: [{
              name: "one",
            },
            {
              name: "two",
            },
          ],
          name: "Alfa",
        },
        {
          roles: [{
            name: "three",
          },],
          name: "Bravo",
        },
        {
          name: "Charlie",
        },
      ];
    </script>

    A generic solution with any number of properties:

    const result = data.reduce((r, {roles, ...user}) => 
      (roles?.length ? roles.forEach(({name: role}) => r.push({...user, role})) : r.push(user), r), []);
    
    console.log(result);
    <script>
      const data = [{
          roles: [{
              name: "one",
            },
            {
              name: "two",
            },
          ],
          name: "Alfa",
        },
        {
          roles: [{
            name: "three",
          },],
          name: "Bravo",
        },
        {
          name: "Charlie",
        },
      ];
    </script>
    Cycles: 1000000 / Chrome/117
    -----------------------------------------------------------
    Alexander            30/min   1.0x   33   30   33   38   34
    Mister Jojo          50/min   1.7x   55   59   53   50   51
    Robin De Schepper   387/min  12.9x  414  404  437  387  450
    mplungjan           397/min  13.2x  426  407  400  446  397
    -----------------------------------------------------------
    https://github.com/silentmantra/benchmark
    
    <script benchmark="1000000">
    
          const data = [{
              roles: [{
                  name: "one",
                },
                {
                  name: "two",
                },
              ],
              name: "Alfa",
            },
            {
              roles: [{
                name: "three",
              },],
              name: "Bravo",
            },
            {
              name: "Charlie",
            },
          ];
          
    // @benchmark Mister Jojo      
    data.reduce( (acc, {roles,name},i ) =>
      {
      if (Array.isArray(roles) && roles.length)
    roles.forEach(({name:role}) => acc.push({name,role}));
      else
    acc.push({name});
      return acc;
      },[]);    
          
    // @benchmark Robin De Schepper      
    data.flatMap((person) => {
        if (Array.isArray(person.roles) && person.roles.length > 0) {
          return person.roles.map((role) => {
            return {
              name: person.name,
              role: role.name,
            };
          });
        }
        return {
          name: person.name,
        };
      });      
          
    // @benchmark mplungjan
    data
      .flatMap(({name,roles}) => roles?.length    ? roles
        .map(role => ({ name, role: role.name })) : ({ name }));
    
    // @benchmark Alexander
    data.reduce((r, {name, roles}) => 
          (roles?.length ? roles.forEach(({name: role}) => r.push({name, role})) : r.push({name}), r), []);
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  4. a reduce, a forEach, and easy to read code… (right?)

    const data = 
      [ { roles : [ { name: 'one' } , { name: 'two' } ] 
        , name  : 'Alfa'
        } 
      , { roles : [ { name: 'three' } ] 
        , name  : 'Bravo'
        } 
      , { name  : 'Charlie' } 
      ];
    
    const result = data.reduce( (acc, {roles,name}) =>
      {
      if (Array.isArray(roles) && roles.length)
        roles.forEach(({name:role}) => acc.push({name,role}));
      else
        acc.push({name});
      return acc;
      },[]);
    
    console.log( result );
    .as-console-wrapper    { max-height: 100% !important;top: 0; }
    .as-console-row::after { display: none !important;           }
    Login or Signup to reply.
  5. If you swap around the name and roles properties in each object and write it out more uniformly, you could make much more sense of what’s going on. I rearranged your data structure below to make the data appear easier to read.

    You could simplify this to the following: Map each item in the array to an object with a name and a role (only if the item has roles).

    const data = [{
      name: "Alfa",        /* 1. name = Alfa      */
      roles: [
        { name: "one" },   /*    - role = one     */
        { name: "two" }    /*    - role = two     */
      ]
    }, {
      name: "Bravo",       /* 2. name = Bravo     */
      roles: [
        { name: "three" }  /*    - role = three   */
      ]
    }, {
      name: "Charlie"      /* 3. name = Charlie   */
    }];
    
    const cleanRoles = (data) =>
      data.flatMap(({ name, roles }) =>
        roles?.length
          ? roles.map(({ name: role }) => ({ name, role }))
          : { name });
    
    console.log(cleanRoles(data));
    .as-console-wrapper { top: 0; max-height: 100% !important; }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search