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

3
  • What have you tried so far? Commented Feb 17, 2021 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 filterfunction for filtering and also some other methods like _pickfrom lodash to subset. Commented Feb 17, 2021 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 keysArraychanges every time. Commented Feb 17, 2021 at 22:26

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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);

1 Comment

This works like a charm. It would be great if you could also add a little bit of explanation to the code. Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.