2

I have an array of objects as following:

const objArray = [
    {scope: "xx", sector: "yy", status: "pending", country: "USA"},
    {scope: "zz", sector: "yy", status: "pending", country: "USA"}
    {scope: "xx", sector: "yy", status: "pending", country: "USA"}
]

And an object as following:

const compare = {scope: "xx", sector: "yy"}

or that one:

const compare = {scope: "xx"}

or that one:

const compare = {scope: "yy"}

I want to loop through the array of objects using one of those three compare objects, and return all objects that match with any one of those three compare examples objects with same scope and sector or scope only or sector only.

I have tried .filter() function, but didn't get it to work:

const filteredCards = objArray.filter(card =>{
    return card.scope   === compare.scope
        && card.sector  === compare.sector;
});
CairoCoder
  • 3,091
  • 11
  • 46
  • 68

3 Answers3

4

Please try this code.

const compare = {scope: "xx"};

const objArray = [
    {scope: "xx", sector: "yy", status: "pending", country: "USA"},
    {scope: "zz", sector: "yy", status: "pending", country: "USA"},
    {scope: "xx", sector: "yy", status: "pending", country: "USA"}
];


var filter_result = objArray.filter(function(item) {
  for (var key in compare) {
    if (item[key] === undefined || item[key] != compare[key])
      return false;
  }
  return true;
});

console.log(filter_result)
wdev733
  • 114
  • 2
  • 12
3

Filter is the way to go, but we may as well make the test dynamic instead of hardcoding the fields in question. Let's write a function that accepts the data array and filters by a query object, returning only items in the array that have a matching value for every property in the query object:

const filterByQuery = (data, query) => 
  data.filter(e => Object.entries(query).every(([k, v]) => e[k] === v))
;

const data = [
  {scope: "xx", sector: "yy", status: "pending", country: "USA"},
  {scope: "zz", sector: "yy", status: "pending", country: "USA"},
  {scope: "xx", sector: "yy", status: "pending", country: "USA"}
];

[
  {scope: "xx", sector: "yy"},
  {scope: "xx"},
  {scope: "yy"},
].forEach(query => console.log(filterByQuery(data, query)));
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Pretty sure you can fit it one line if you tried ;) – Halcyon Aug 08 '19 at 23:13
  • 2
    Maybe on your monitor! – ggorlen Aug 08 '19 at 23:16
  • "Maybe on your monitor!" rofl. One-liners are nice in some single-use-cases, however, here on SO readability is preferable. – Pinke Helga Aug 08 '19 at 23:20
  • Your vibe is wrong. I think one-liners are less readable to the public. Furthermore they are less reusable. – Pinke Helga Aug 08 '19 at 23:28
  • @ggorlen actually that answer is problematic in 1995 because of the direct comparison with `undefined`. Apparently it's allowed now, I did not know that before today https://stackoverflow.com/questions/2778901/how-to-compare-variables-to-undefined-if-i-don-t-know-whether-they-exist – Halcyon Aug 08 '19 at 23:32
  • To further improve the comparision method, one could implement a feature switch and use `Object.is` instead of `===` if available (or use the polyfill). https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/is – Pinke Helga Aug 08 '19 at 23:38
  • I'm not sure if my English is as bad as you do not understand what I am trying to say. I don't see any one-liner in your code. It is wellformed and splitted into a separate callback and a looped invocation. It is readable formatted IN OPPOSITE to a one-liner. Actually this answer did get my unique UV. – Pinke Helga Aug 08 '19 at 23:46
  • Oh, sorry, I misunderstood. Thanks for clarifying. – ggorlen Aug 08 '19 at 23:48
1

I think the issue is that your filter has optional fields so if a field is not defined in the filter any value will do. I think this will work:

const filteredCards = objArray.filter(card =>{
    return (typeof compare.scope === "undefined" || card.scope === compare.scope)
        && (typeof compare.sector === "undefined" || card.sector === compare.sector)
        && (typeof compare.status === "undefined" || card.status === compare.status)
        && (typeof compare.country === "undefined" || card.country === compare.country);
});

Either the filter does not specify a value for the field, or the value matches. Repeat for each field.

Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • That's what I thought too. You can write in a shorter way using `in` operator like `!'scope' in compare || ...` – marekful Aug 08 '19 at 23:04
  • @marekful oh that's fancy. I'm old skool =) – Halcyon Aug 08 '19 at 23:07
  • Are we sure it's desirable to hardcode all of the relevant fields? Seems like the same scenario as [this](https://stackoverflow.com/questions/57421569/javascript-loop-through-array-of-objects-using-against-an-object/57421708#comment101322533_57421639) – ggorlen Aug 08 '19 at 23:15
  • @ggorlen it depends. If you're only ever going to have 4 fields either way is fine. Glen's if-else tree is going to grow exponentially, whereas this solution is linear. I mostly chose this verbatim way of writing the filter to explain what's going on. With something as esoteric as `(([k, v]) => e[k] === v)` I have to read it twice to make sure it's doing what I think it's doing. And you have to check you're _eaching_ over the filter and not the data etc. It's not as nice to read I think. Also my linting tool would give you a big fat _no_ .. so xD – Halcyon Aug 08 '19 at 23:19
  • Sure, that's valid, but complexity aside, the function needs to be rewritten any time the object changes, and we're stuck with a big conditional which linters also correctly complain about. My preference in that case would be to write it "vanilla"-style but still not use literals as done [here](https://stackoverflow.com/a/57421663/6243352). I'm not really married to modern JS, but I also don't mind it either. – ggorlen Aug 08 '19 at 23:24