Search code examples
javascriptecmascript-6ecmascript-5

Grouping by fields after reduce is not working in JavaScript


There is a complex object and based on an array which is given as an input I need to modify its properties. Illustration is shown below. If the "field" is same , add them to "or" array .If its different "field" add them to "and" array along with its "value". I am using Set to get keys from both source and input and using them to group based on its keys. Also whenever there are duplicates .ie., suppose the "filterObj" already has the same (field, value) pair. Be it in "and" or inside "or",Then don't add it in the final object

Sandbox: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-dpvis

There is a TestCases file in the sandbox which its needs to pass

let filterObj = {
  feature: "test",
  filter: {
    and: [{ field: "field2" }]
  }
};
let obj = [{ field: "field2", value: "3" }];
let all_filters = [];
if (filterObj.filter.and && filterObj.filter.and.hasOwnProperty("or")) {
  all_filters = [...filterObj.filter.and.or];
} else if (filterObj.filter.and) {
  all_filters = [...filterObj.filter.and];
}
const all_objs = [...obj, ...all_filters];
const uniqKeys = all_objs.reduce(
  (acc, curr) => [...new Set([...acc, curr.field])],
  []
);
const updateItems = uniqKeys.map(obj => {
  const filter_items = all_objs.filter(item => item.field === obj);
  let resultObj = {};
  if (filter_items && filter_items.length > 1) {
    resultObj.or = [...filter_items];
  } else if (filter_items && filter_items.length === 1) {
    resultObj = { ...filter_items[0] };
  }
  return resultObj;
});
var result = { ...filterObj, filter: { and: [...updateItems] } };
console.log(result);


Solution

  • Try it. I redid the implementation, it happened more universally. Parses any filters according to your algorithm that it finds. All test cases are worked.

    Sandbox link: https://codesandbox.io/s/optimistic-mirzakhani-pogpw-so-i1u6h

    let filterObj = {
      feature: "test",
      filter: {
        and: [
          {
            field: "field1",
            value: "2"
          }
        ]
      }
    };
    
    let obj = [
      {
        field: "field1",
        value: "2"
      },
      {
        field: "field1",
        value: "1"
      }
    ];
    
    var FilterController = function(filter) {
      var self = this;
      self.filter = filter;
      // encapsulated map of objects by fields
      var storeMap = {};
      // counter of objects
      var counter = 0;
    
      var tryPutObjectToMap = function(object) {
        if (typeof object === "object") {
          // get type for grouping
          var objectType = self.getObjectGroupType(object);
          if (objectType !== null) {
            // cheack have group
            if (!storeMap.hasOwnProperty(objectType)) {
              storeMap[objectType] = [];
            }
    
            var duplicate = storeMap[objectType].find(function(sObject) {
              return self.getObjectValue(sObject) === self.getObjectValue(object);
            });
    
            // check duplicate
            if (duplicate === undefined) {
              counter++;
              storeMap[objectType].push(object);
            } else {
              // TODO: Handle duplicates
            }
          } else {
            // TODO: handle incorrect object
          }
        }
      };
    
      // get filter structure from map
      var getFilterStructureFromMap = function() {
        var result = {};
    
        // check exists root filter and filed if have objects
        if (counter > 0) {
          result["and"] = [];
        }
    
        for (var key in storeMap) {
          if (storeMap.hasOwnProperty(key)) {
            var array = storeMap[key];
            if (array.length > 1) {
              result["and"].push({
                // clone array
                or: array.slice()
              });
            } else {
              result["and"].push(array[0]);
            }
          }
        }
        return result;
      };
    
      // rewrite and get current filter
      // if you need^ create new object for result
      self.rewriteAndGetFilter = function() {
        self.filter.filter = getFilterStructureFromMap();
        return self.filter;
      };
    
      // not prototype function for have access to storeMap
      self.putObjects = function(objects) {
        if (Array.isArray(objects)) {
          // recursive push array elements
          objects.forEach(element => self.putObjects(element));
          // handle array
        } else if (typeof objects === "object") {
          // handle object
          if (objects.hasOwnProperty("and") || objects.hasOwnProperty("or")) {
            for (var key in objects) {
              //no matter `or` or `and` the same grouping by field
              // inner object field
              if (objects.hasOwnProperty(key)) {
                self.putObjects(objects[key]);
              }
            }
          } else {
            // filters props not found, try push to store map
            tryPutObjectToMap(objects);
          }
        } else {
          // TODO: Handle errors
        }
      };
    
      if (self.filter.hasOwnProperty("filter")) {
        // put and parse current objects from filter
        self.putObjects(self.filter.filter);
      }
    };
    
    // function for grouping objects.
    // for you get filed name from object.
    // change if need other ways to compare objects.
    FilterController.prototype.getObjectGroupType = function(obj) {
      if (typeof obj === "object" && obj.hasOwnProperty("field")) {
        return obj.field;
      }
      return null;
    };
    
    // get object value
    FilterController.prototype.getObjectValue = function(obj) {
      if (typeof obj === "object" && obj.hasOwnProperty("value")) {
        return obj.value;
      }
      return null;
    };
    
    var ctrl = new FilterController(filterObj);
    ctrl.putObjects(obj);
    var totalFilter = ctrl.rewriteAndGetFilter();
    console.log(totalFilter);
    console.log(JSON.stringify(totalFilter));
    
    

    EDIT 1

    I did not change the logic; I made a function based on it.

    let filterObj = {
      feature: "test",
      filter: {
        and: [
          {
            field: "field1",
            value: "2"
          }
        ]
      }
    };
    
    let obj = [
      {
        field: "field1",
        value: 2
      },
      {
        field: "field1",
        value: "1"
      }
    ];
    
    function appendToFilter(filter, inputObjects) {
      var storeMap = {};
      var counter = 0;
      var handlingQueue = [];
      // if filter isset the appen to handling queue
      if (filter.hasOwnProperty("filter")) {
        handlingQueue.push(filter.filter);
      }
      // append other object to queue
      handlingQueue.push(inputObjects);
      // get first and remove from queue
      var currentObject = handlingQueue.shift();
      while (currentObject !== undefined) {
        if (Array.isArray(currentObject)) {
          currentObject.forEach(element => handlingQueue.push(element));
        } else if (typeof currentObject === "object") {
          if (currentObject.hasOwnProperty("and") || currentObject.hasOwnProperty("or")) {
            for (var key in currentObject) {
              if (currentObject.hasOwnProperty(key)) {
                handlingQueue.push(currentObject[key]);
              }
            }
          } else {
            // TODO: append fild exists check
            if (currentObject.field) {
              if (!storeMap.hasOwnProperty(currentObject.field)) {
                storeMap[currentObject.field] = [];
              }
              var localValue = currentObject.value;
              // check duplicate
              if (storeMap[currentObject.field].find(object => object.value === localValue) === undefined) {
                counter++;
                storeMap[currentObject.field].push(currentObject);
              } 
            } 
          }
        }
    
        currentObject = handlingQueue.shift();
      }
    
      // create new filter settings
    
      var newFilter = {};
    
      // check exists root filter and filed if have objects
      if (counter > 0) { newFilter["and"] = []; }
    
      for (var storeKey in storeMap) {
        if (storeMap.hasOwnProperty(storeKey)) {
          var array = storeMap[storeKey];
          if (array.length > 1) {
            newFilter["and"].push({
              // clone array
              or: array.slice()
            });
          } else {
            newFilter["and"].push(array[0]);
          }
        }
      }
      filter.filter = newFilter;
    }
    
    // update filterObj
    appendToFilter(filterObj, obj);
    console.log(filterObj);
    

    EDIT 2,3 (UPDATED)

    With others objects support.

    export function appendToFilter(filter, inputObjects) {
      var storeMap = {};
      var others = [];
      var counter = 0;
      var handlingQueue = [];
      // if filter isset the appen to handling queue
      if (filter.hasOwnProperty("filter") && filter.filter.hasOwnProperty("and")) {
        handlingQueue.push(filter.filter.and);
      }
      // append other object to queue
      handlingQueue.push(inputObjects);
      // get first and remove from queue
      var currentObject = handlingQueue.shift();
      while (currentObject !== undefined) {
        if (Array.isArray(currentObject)) {
          currentObject.forEach(element => handlingQueue.push(element));
        } else if (typeof currentObject === "object") {
          if (
            currentObject.hasOwnProperty("and") ||
            currentObject.hasOwnProperty("or")
          ) {
            for (var key in currentObject) {
              if (currentObject.hasOwnProperty(key)) {
                handlingQueue.push(currentObject[key]);
              }
            }
          } else {
            // TODO: append fild exists check
            if (currentObject.field) {
              if (!storeMap.hasOwnProperty(currentObject.field)) {
                storeMap[currentObject.field] = [];
              }
              var localValue = currentObject.value;
              // check duplicate
              if (
                storeMap[currentObject.field].find(
                  object => object.value === localValue
                ) === undefined
              ) {
                counter++;
                storeMap[currentObject.field].push(currentObject);
              }
            } else {
              // handle others objects^ without field "field"
              counter++;
              others.push(currentObject);
            }
          }
        }
        currentObject = handlingQueue.shift();
      }
      // create new filter settings
      var newFilter = {};
      // check exists root filter and filed if have objects
      if (counter > 0) {
        newFilter["and"] = [];
      }
      for (var storeKey in storeMap) {
        if (storeMap.hasOwnProperty(storeKey)) {
          var array = storeMap[storeKey];
          if (array.length > 1) {
            newFilter["and"].push({
              // clone array
              or: array.slice()
            });
          } else {
            newFilter["and"].push(array[0]);
          }
        }
      }
      // Append others to result filter
      others.forEach(other => newFilter["and"].push(other));
      filter.filter = newFilter;
    }