we need to mask some specific fields in an object because they are personal identifying info, we would like to do this selectively because in large objects it is possible that nested properties might have the same name but they might not be required to be hidden.
the following function uses recursion to traverse the properties of an object and it accumulates the path to each property, this way the consumer can pass in the paths that it wants hidden and upon detecting these paths it proceeds to hide them.
export default function maskProperties(
obj: Record<string, any>,
pathsToMask: string[],
accumulatedPath = '',
) {
for (const property in obj) {
if (obj.hasOwnProperty(property)) {
const currentPath = accumulatedPath + '.' + property;
if (pathsToMask.includes(currentPath)) {
obj[property] = '[HIDDEN]';
}
if (typeof obj[property] == 'object') {
maskProperties(obj[property], pathsToMask, currentPath);
}
}
}
}
usage:
const data = {
account: {
tenant: 'some',
email: '[email protected]',
phone: 1234567,
names: [
{ givenName: 'Jason', familyName: 'Mays', type: 'primary' },
],
},
};
maskProperties(data, ['.payload.account.email', '.payload.account.phone', '.payload.account.names']);
result:
{
payload: {
account: {
tenant: "some",
email: "[HIDDEN]",
phone: "[HIDDEN]",
names: "[HIDDEN]"
}
}
}
If however you would like to mask all properties in an object you could use the following:
function mask(propValue) {
if (!propValue) return propValue;
const regex = /(?<!^).(?!$)/g;
return propValue.toString().replace(regex, '*');
}
function doMasking(obj: Record<string, any>) {
for (const property in obj) {
if (Object.prototype.hasOwnProperty.call(obj, property)) {
if (typeof obj[property] == 'object') {
doMasking(obj[property]);
} else {
obj[property] = mask(obj[property]);
}
}
}
}
export function maskProperties(obj: Record<string, any> | undefined) {
if (!obj) {
return undefined;
}
const copy = { ...obj };
doMasking(copy);
return copy;
}