Search code examples
javascriptnode.jsutility

Javascript deep object filter


I am building deepFilterObject.js utility function. Tried many, none of them work. My object before the filter is something like this:

{
  userId: '6501747e258044dcfcf8c765',
  name: 'Morning workout',
  exercises: [
    { exercise: 'Oblique Crunch', sets: [Array] },
    { exercise: 'Chest Fly (Dumbbell)', sets: [Array] },
    { exercise: 'Jump Squat', sets: [Array] }
  ],
  metadata: {
    evaluation: null,
    type: 'realtime',
    copy: false,
    note: 'This is a note. (optional)',
    estimatedDurationMinutes: 60,
    description: 'Some description. (optional)',
    dates: {
      scheduled: 'Tue Aug 08 2023 10:55:56',
      completed: null,
      creation: 'Tue Aug 01 2023 12:51:35'
    },
    access: {
      authorId: 'objectId',
      type: 'public/private (ENUM)',
      comments: [Array],
      copies: 0
    }
  }
}

Here is my function call:

const filteredBody = deepFilterObject(object, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');

I expect something like this object as a result:

{
  userId: '6501747e258044dcfcf8c765',
  name: 'Morning workout',
  exercises: [
    { exercise: 'Oblique Crunch', sets: [Array] },
    { exercise: 'Chest Fly (Dumbbell)', sets: [Array] },
    { exercise: 'Jump Squat', sets: [Array] }
  ],
  metadata: {
    evaluation: null,
    access: {
      type: 'public/private (ENUM)',
    }
  }
}

Here is my code for my utility, which I import into my controller as "deepFilterObject":

function createObjectFromNestedArray(inputArray, sourceObject) {
  // Base case: If the input array is empty, return the sourceObject.
  if (inputArray.length === 0) {
    return sourceObject;
  }

  // Get the current key from the inputArray.
  const [currentKey, ...remainingKeys] = inputArray[0];

  // Check if the current key exists in the sourceObject.
  if (sourceObject.hasOwnProperty(currentKey)) {
    // If it exists, recursively call the function with the remaining keys and the existing object.
    sourceObject[currentKey] = createObjectFromNestedArray(remainingKeys, sourceObject[currentKey]);
  } else {
    // If it doesn't exist, create a new object and attach it.
    sourceObject[currentKey] = {};

    // Recursively call the function with the remaining keys and the newly created object.
    sourceObject[currentKey] = createObjectFromNestedArray(remainingKeys, sourceObject[currentKey]);
  }

  // Return the modified sourceObject.
  return sourceObject;
}

function deepFilterObject(obj, ...allowedFields) {
  console.log(obj);
  //   const newObj = {};
  //   Object.keys(obj).forEach(el => {
  //     if (allowedFields.includes(el)) newObj[el] = obj[el];
  //   });

  const deepAllowedFields = allowedFields
    .filter(allowedField => allowedField.split('.').length > 1)
    .map(allowedField => allowedField.split('.'));

  const finalObj = createObjectFromNestedArray(deepAllowedFields, obj);
  //   console.log(finalObj);

  //   return newObj;
}

module.exports = deepFilterObject;

Any help is appreciated! Thanks in advance!


Solution

  • The most easy way is to copy each path in order from source to a new object, creating the path in new object if it's missing

    function deepFilterObject(obj, ...paths) {
        let result = {};
        for (let s of paths) {
            let from = obj, to = result;
            let path = s.split('.');
            let prop = path.pop();
            for (let p of path) {
                to = (to[p] ??= {});
                from = from[p];
            }
            to[prop] = from[prop];
        }
        return result;
    }