Improve reusability of my data transformation functions

79 views Asked by At

I have currently done with my code transformation on my api data by its roleId. However, I need to display another view which will group the data according to which projectId the users are in.

I can simply copy and paste and create another one for projectIds data transformation, however, I feel that my approach may be messy and not easily reusable. So I would like to ask if there is a better way to do this?

Because i cannot simply swap roleIds in the function to projectIds by putting rolesId or projectIds in a variable to be reused in the function.

Can anyone help me please ?

Code for the api data transformation to dislay in ant design tree data table:

let apiData = [
  {
    email: "[email protected]",
    permissionIds: null,
    roleIds: ["raa", "baa", "caa"],
    projectIds: ["1aa", "3aa"]
  },
  {
    email: "[email protected]",
    permissionIds: null,
    roleIds: ["baa", "caa"],
    projectIds: ["1aa", "2aa", "3aa"]
  },
  {
    email: "[email protected]",
    permissionIds: null,
    roleIds: ["caa"],
    projectIds: ["1aa"]
  },
  {
    email: "[email protected]",
    permissionIds: null,
    roleIds: [],
    projectIds: []
  }
];

        //Isolate and transform data by roleId
        const transData = apiData.reduce((arr, item) => {
          let formatted = item.roleIds.map((id) => {
            return {
              roleIds: id,
              children: [{ ...item, roleIds: id }]
            };
          });

          return [...arr, ...formatted];
        }, []);

        //Group transformed data by roleIds
        const findMatch = (arr, roleIds) =>
          arr.find((item) => item.roleIds === roleIds);

        const groupArray = (originalArr) => {
          return Array.isArray(originalArr)
            ? originalArr.reduce((previousObj, obj) => {
              if (findMatch(previousObj, obj.roleIds)) {
                findMatch(previousObj, obj.roleIds).children.push(...obj.children);
              } else {
                previousObj.push(obj);
              }
              return previousObj;
            }, [])
            : "Need an array";
        };
        //Call the group roleId function on transformed data by roleId
        const userRoledata = groupArray(transData);

        //Add key to parent and children
        let key = 1;
        userRoledata.forEach((item) => {
          item.key = key++;
          item.children.forEach((child) => {
            child.key = key++;
          });
        });

        setData(userRoledata); //this will be dataSource for table rendering in ant design

What will the data transformed display when used as dataSource in ant design:

If grouped by roleIds:

[
  {
    "roleIds": "raa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "raa",
        "projectIds": [
          "1aa",
          "3aa"
        ],
        "key": 2
      }
    ],
    "key": 1
  },
  {
    "roleIds": "baa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "baa",
        "projectIds": [
          "1aa",
          "3aa"
        ],
        "key": 4
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "baa",
        "projectIds": [
          "1aa",
          "2aa",
          "3aa"
        ],
        "key": 5
      }
    ],
    "key": 3
  },
  {
    "roleIds": "caa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "caa",
        "projectIds": [
          "1aa",
          "3aa"
        ],
        "key": 7
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "caa",
        "projectIds": [
          "1aa",
          "2aa",
          "3aa"
        ],
        "key": 8
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": "caa",
        "projectIds": [
          "1aa"
        ],
        "key": 9
      }
    ],
    "key": 6
  }
]

If grouped by projectIds:

[
  {
    "projectIds": "1aa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "raa",
          "baa",
          "caa"
        ],
        "projectIds": "1aa",
        "key": 2
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "baa",
          "caa"
        ],
        "projectIds": "1aa",
        "key": 3
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "caa"
        ],
        "projectIds": "1aa",
        "key": 4
      }
    ],
    "key": 1
  },
  {
    "projectIds": "3aa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "raa",
          "baa",
          "caa"
        ],
        "projectIds": "3aa",
        "key": 6
      },
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "baa",
          "caa"
        ],
        "projectIds": "3aa",
        "key": 7
      }
    ],
    "key": 5
  },
  {
    "projectIds": "2aa",
    "children": [
      {
        "email": "[email protected]",
        "permissionIds": null,
        "roleIds": [
          "baa",
          "caa"
        ],
        "projectIds": "2aa",
        "key": 9
      }
    ],
    "key": 8
  }
]
1

There are 1 answers

11
W.S. On BEST ANSWER

Define a transform function with 2 parameter. First the apiData, which is the data you want to transform and secondly the transformation_key which is a string of either roleIds or projectIds.

Within this function you first to generate an object with the different roleIds/projectIds as keys and for each key an array of all the items included.

To do so you make use of a reducer and loop over the items

apiData.reduce((obj, item) => {
  if (!item[transformation_key]) return obj; // in case item[transformation_key] is null, you can skip the item an just return the item as is.
  ... // if not, we've to reduce over the array of roleIds/projectIds within the item as well.
}, {}) // {} is the new object (`obj` refers to this within the reducer)

Within each item we also have to loop over all the items in the roleIds/projectIds of that item, so we add a second/inner reducer.

// item[transformation_key] is the array of roleIds/projectIds within your item.
item[transformation_key].reduce((cur, id) => {
  // `cur` is actually the same object as the `obj` from the outer reducer.
  if (!cur[id]) cur[id] = [] // if the key/id doesn't excist yet on the object, we set it equal to an empty array.
  cur[id].push({
    ...item,
    [transformation_key]: id
  }) // we push the item to the array (using the spread operator and updating the value for the `transformation_key` within the item.
  return cur // you must return the object `cur`.
}, obj) // we pass to `obj` from the outer reducer into the inner reducer.

This will generate an object like

const transformedObject = {
  [roleIds/projectIds] : [
    ... all the children
  ]
}

next we map the ids to the required output

return Object.keys(transformedObject).map(key => {
  return {
    [transformation_key]: key,
    children: transformedObject[key]
  }
})

To summarize

function transform(apiData, transformation_key) {

  if (!(transformation_key == 'roleIds' || transformation_key == 'projectIds')) throw new Error("Choose either 'roleIds' or 'projectIds' as a transformation_key")

  const transformedObject = apiData
    .reduce((obj, item) => {
      if (!item[transformation_key]) return obj;
      return item[transformation_key].reduce((cur, id) => {
        if (!cur[id]) cur[id] = []
        cur[id].push({
          ...item,
          [transformation_key]: id
        })
        return cur
      }, obj)
    }, {});
    
  return Object.keys(transformedObject).map(key => {
    return {
      [transformation_key]: key,
      children: transformedObject[key]
    }
  })
}

const transDataByRoleIds = transform(res.data, 'roleIds')
const transDataByProjectIds = transform(res.data, 'projectIds')