skip to Main Content

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!

3

Answers


  1. I would do it like this:

    const object = {
      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
        }
      }
    }
    
    const filteredBody = deepFilterObject(object, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');
    
    document.write(`<pre>${JSON.stringify(filteredBody, null, 4)}</pre>`)
    
    function deepFilterObject(object, ...paths) {
      // parse paths to get the path object
      const pathObject = preparePathObject(paths)
      
      // recursive function
      function filterObject(obj, path) {
        const newObject = {}
        // for each entry in the path object
        for (const [key, subPath] of Object.entries(path)) {
          // get sub object from the original object
          const subObj = obj[key]
          // if the sub object is an object, but not null or array
          if (typeof subObj === 'object' && subObj !== null && !Array.isArray(subObj)) {
            // run recursion
            newObject[key] = filterObject(subObj, subPath)
          } else {
            // else assign the object to the new object
            newObject[key] = subObj
          }
        }
        
        return newObject
      }
      
      return filterObject(object, pathObject)
    }
    
    // creates nested path object
    function preparePathObject(paths) {
      const obj = {}
      for (const path of paths) {
        const keys = path.split('.')
        let current = obj
        for (const key of keys) {
          if (!(key in current)) {
            current[key] = {}
          }
          current = current[key]
        }
      }
      return obj
    }
    Login or Signup to reply.
  2. Generate a copy function, cache it and execute it. It’s good if you process a lot of item:

    const cache = {};
    function deepFilterObject(obj, ...props){
    
      let fn = cache[props.join(',')];
      if(fn){
        return fn(obj);
      }
      
      const assignments = props.map(prop => {
        let name = 'out.' + prop;
        const ps = prop.split('.');
        if(ps.length > 1){
          const last = ps.pop();
          name = 'out';
          ps.forEach(prop => name = '(' + name + '.' + prop + '??={})');
          name += '.' + last;
        }
        
        return name + ' = obj.' + prop;
      
      }).join(';n');
      
      fn = cache[props.join(',')] = new Function('obj', `
        const out = {};
        ${assignments};
        return out;
      `);  
      
      return fn(obj);
     
    }
    
    const filteredBody = deepFilterObject(obj, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');
    
    $pre.innerText = JSON.stringify(filteredBody, null, 4);
    <script>
    const obj ={
      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
        }
      }
    }
    </script>
    <pre id="$pre"></pre>

    And a benchmark:

    Cycles: 1000000 / Chrome/116
    ---------------------------------------------------
    Alexander;   144/min  1.0x  146  171  144  153  150
    Dimava       193/min  1.3x  217  193  208  207  231
    Konrad       568/min  3.9x  568  579  576  618  577
    ---------------------------------------------------
    https://github.com/silentmantra/benchmark
    
    <script benchmark="1000000">
    
        const obj ={
          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
            }
          }
        }
        
        // @benchmark Konrad
        function deepFilterObject3(object, ...paths) {
      // parse paths to get the path object
      const pathObject = preparePathObject(paths)
      
      // recursive function
      function filterObject(obj, path) {
        const newObject = {}
        // for each entry in the path object
        for (const [key, subPath] of Object.entries(path)) {
          // get sub object from the original object
          const subObj = obj[key]
          // if the sub object is an object, but not null or array
          if (typeof subObj === 'object' && subObj !== null && !Array.isArray(subObj)) {
            // run recursion
            newObject[key] = filterObject(subObj, subPath)
          } else {
            // else assign the object to the new object
            newObject[key] = subObj
          }
        }
        
        return newObject
      }
      
      return filterObject(object, pathObject)
    }
    
    // creates nested path object
    function preparePathObject(paths) {
      const obj = {}
      for (const path of paths) {
        const keys = path.split('.')
        let current = obj
        for (const key of keys) {
          if (!(key in current)) {
            current[key] = {}
          }
          current = current[key]
        }
      }
      return obj
    }
    // @run
     deepFilterObject3(obj, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');   
        // @benchmark Dimava
        function deepFilterObject2(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;
        } 
        
        //@run
         deepFilterObject2(obj, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');
        
        // @benchmark Alexander;
    
        const cache = {};
        
        function deepFilterObject(obj, ...props){
    
          let fn = cache[props.join(',')];
          if(fn){
            return fn(obj);
          }
          
          const assignments = props.map(prop => {
            let name = 'out.' + prop;
            const ps = prop.split('.');
            if(ps.length > 1){
              const last = ps.pop();
              name = 'out';
              ps.forEach(prop => name = '(' + name + '.' + prop + '??={})');
              name += '.' + last;
            }
            
            return name + ' = obj.' + prop;
          
          }).join(';n');
          
          fn = cache[props.join(',')] = new Function('obj', 'const out = {};'+assignments+';return out');
          
          return fn(obj);
         
        }
        
        //@run;
    
        deepFilterObject(obj, 'name', 'exercises', 'metadata.evaluation', 'metadata.access.type');
    
    </script>
    
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  3. 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;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search