1

I have an array of object which needs to be filtered based upon the certain value :

let data = 
[
  {
    "region": 28,
    "nuts": 33,
    "score" : 26
   }, 
  {
    "region": 18,
    "nuts": 22,
    "score" : 21
   }
]

The other array includes keys for which the above object is to be filtered and also subsetted :


// this changes everytime 
// it may include ALL the keys or only ONE key of above object 
let keysArray = ["region", "score"]
(OR )
let keysArray = ["region", "nuts"]

Now if the data needs to be filtered for region and score above 25 then it should return the following :

// when => keysArray = ["region", "score"]
// region && score >= 25 
// expected output
 [
  {
    "region": 28,
    "score" : 26
   }
 ]

// when => keysArray = ["region", "nuts"]
// region &&  nuts >= 25 
// expected output
 [
  {
    "region": 28,
    "nuts" : 33
   }
 ]

How can achieve this? Thanks in advance.

PS : This does not answer the question - filtering an array of objects based on another array in javascript

relu
  • 333
  • 1
  • 3
  • 18
  • What have you tried so far? – codeth Feb 17 '21 at 22:09
  • I can filter and subset when `keysArray` is static. But it is dynamic and I am not able to find the solution around it. I used `filter`function for filtering and also some other methods like `_pick`from lodash to subset. – relu Feb 17 '21 at 22:14
  • No it does not. In my case the parent array of object `data` is to be scanned and subset for its keys within `keysArray`. And this `keysArray`changes every time. – relu Feb 17 '21 at 22:26

3 Answers3

1

I created a working snippet.

let data = [{
    region: 28,
    nuts: 33,
    score: 26
  },
  {
    region: 18,
    nuts: 22,
    score: 21
  }
];

// this changes everytime
// it may include ALL the keys or only ONE key of above object
let keysArray = ["region", "score"];

// Write Javascript code!
const appDiv = document.getElementById("app");
appDiv.innerHTML = `<h1>JS Starter</h1>`;

const resultDiv = document.getElementById("result");

filterData(keysArray, 25).forEach(item => {
  resultDiv.innerHTML += `<li>region: ${item.region}, score: ${
    item.score
  }</li>`;
});

function filterData(keys, val) {
  let result = [];

  data.forEach(dataItem => {
    let isEligible = false;

    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      isEligible = dataItem[key] >= val;
      if (!isEligible) break;
    }

    if (isEligible) result.push(dataItem);
  });

  return result;
}
<div id="app"></div>

<ul id="result"></ul>

Working demo in stackblitz: https://stackblitz.com/edit/js-swutjy?embed=1&file=index.js

Dharman
  • 30,962
  • 25
  • 85
  • 135
1

let data = 
[
  {
    "region": 28,
    "nuts": 33,
    "score" : 26
   }, 
  {
    "region": 18,
    "nuts": 22,
    "score" : 21
   }
];

let keysArray = ["region", "nuts"];

const hasValAboveThreshold = (item, threshold) => {
  return keysArray.every((key) => item[key] > threshold);
}

const getItem = (item) => {
  return keysArray.reduce((acc, key) => {
    acc[key] = item[key];
    return acc;
  }, {});
}

const output = data.reduce((acc, val) => {
  if (hasValAboveThreshold(val, 25)) {
    acc.push(getItem(val));
  }  
  return acc;
}, []);

console.log(output);
fyasir
  • 2,924
  • 2
  • 23
  • 36
  • 1
    This works like a charm. It would be great if you could also add a little bit of explanation to the code. Thanks – relu Feb 18 '21 at 14:10
1

With Underscore (or Lodash), you can write much shorter code.

// The function that will do the trick.
function subsetFilter(data, keys, predicate) {
    return _.chain(data)
        .map(_.partial(_.pick, _, keys))
        .filter(_.partial(_.every, _, predicate))
        .value();
}

// Usage.
subsetFilter(data, ['region', 'nuts'], x => x >= 25);

Let's break this into pieces.

function subsetFilter(data, keys, predicate) {

We create a function with three parameters: the data to be subsetted and filtered, the array of keys that may be variable, and the predicate, which can be any function that returns a boolean. For example, I used x => x >= 25 above to state that the properties should be greater than or equal to 25.

_.chain(data)

We wrap the data in a special wrapper object which will allow us to chain multiple Underscore functions, each processing the result of the previous function in the chain (doc).

_.pick

This is a function that takes two arguments: an object and an array of keys. It returns a copy of the object with only the properties of which the names were listed in the array of keys (doc). For example:

_.pick({a: 1, b: 2}, ['a', 'c']) // returns {a: 1}

Next,

_.partial(_.pick, _, keys)

This transforms _.pick into a new function, where the second argument has already been pre-filled with keys. The _ signifies that this modified version of _.pick is still looking for its first argument (doc). It is equivalent to the following function:

function(obj) {
    return _.pick(obj, keys);
}

Completing this line,

.map(_.partial(_.pick, _, keys))

We call _.map on the chained data wrapper, producing a new array in which each element has been transformed through the function we created using _.partial and _.pick. In our example, the chain wrapper contains the following data at this point:

[
  {
    "region": 28,
    "nuts": 33
   }, 
  {
    "region": 18,
    "nuts": 22
   }
]

(so basically still the same data, except that the score properties are no longer there because we didn't _.pick them.)

_.partial(_.every, _, predicate)

Again, we use _.partial to create a modified version of a function where the second argument has already been pre-filled. This time, the base function is _.every, which takes an array or object and a predicate. It returns true if the predicate returns true for each element of the array or each property of the object, otherwise false. The pre-filled second argument is the predicate parameter of our subsetFilter function. Basically we are stating the following here: give this function an object, and it will tell you whether all of its properties meet predicate.

.filter(_.partial(_.every, _, predicate))

We _.filter the intermediate result in our chain with the function we just created using _.partial and _.every. We keep only the objects in the array of which each property meets predicate. At this point, our chain wrapper contains the following value:

[
  {
    "region": 28,
    "nuts": 33
   }
]

which is the result you want, but it is still wrapped. So as a final step, we remove the wrapper (doc):

.value();

This is the end of our expression and the result that we return from our function.

Julian
  • 4,176
  • 19
  • 40