So a few things for starters: I’ll simplify this template further, but it has the structure that should allow you to solve this. You don’t need Object.values
here, and tbh you don’t need Object.keys
either (although it can make it a bit nicer to iterate).
I think you’re misunderstanding how filter
works. It isn’t a generic loop, you don’t use push
with it.
The function you give to filter
should return true/false depending on some condition. That’s all: you don’t need to manually build an array: filter
will build it for you, filling it with the values that pass the test defined by the callback function (ie they return true
), and skipping those that fail (ie they return false
).
function whatIsInAName(collection, source) {
return collection.filter(currentItemIsAMatch);
}
currentItemIsAMatch
is the placeholder for the thing you need here: it has to be a function that accepts the current item in the array (an object), and returns true or false by comparing it with the source
object.
I’m going to use concrete values as well: this is what I’ll use:
collection = [{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }]
source = { "apple": 1, "bat": 2 }
So this can’t work:
item[sourceKeys] == source[sourceKeys]
item
is the current value in the array. Subsitute in the values. The first item is the object { "apple": 1, "bat": 2 }
. So you’re writing:
{ "apple": 1, "bat": 2 }[["apple", "bat"]] == { "apple": 1, "bat": 2 }[["apple", "bat"]]
That doesn’t look right.
The function should take each one of those objects in collection
and return true or false:
{ "apple": 1, "bat": 2 } // true
{ "bat": 2 } // false
{ "apple": 1, "bat": 2, "cookie": 2 } // true
Why though? Well
item = { "apple": 1, "bat": 2 }
source = { "apple": 1, "bat": 2 }
// true because item.apple === source.apple and item.bat === source.bat
item = { "apple": 1, "bat": 2 }
source = { "bat": 2 }
// false because item.apple !== source.apple even though item.bat === source.bat
item = { "apple": 1, "bat": 2 }
source = { "apple": 1, "bat": 2, "cookie": 2 }
// true because item.apple === source.apple and item.bat === source.bat
So to use the dummy name I’ve given for the filter callback, if the function is ran on its own:
currentItemIsAMatch({ "apple": 1, "bat": 2 }) // true
currentItemIsAMatch({ "bat": 2 }) // false
currentItemIsAMatch({ "apple": 1, "bat": 2, "cookie": 2 }) // true
Because, to use pseudocode:
FUNCTION currentItemIsAMatch(item):
FOR EACH key IN source:
IF source[key] IS EQUAL TO item[key]:
# it's a match, keep going
CONTINUE
ELSE IF source[key] IS NOT EQUAL TO item[key]:
# the key/value isn't in the item, fail the test
RETURN false
ENDIF
ENDFOR
# we've got to the end of the loop, so the test passed
RETURN true
Which can be rewritten to return as soon as the test fails:
FUNCTION currentItemIsAMatch(item):
FOR EACH key IN source:
IF source[key] IS NOT EQUAL TO item[key]:
# the key/value isn't in the item, fail the test immediately
RETURN false
ENDIF
ENDFOR
# we've got to the end of the loop, so the test passed
RETURN true
So for currentItemIsAMatch({ "apple": 1, "bat": 2 })
, I loop over source
.
-
source["apple"]
is equal to item["apple"]
- continue
-
source["bat"]
is equal to item["bat"]
- continue
- loop has finished, return true
So for currentItemIsAMatch({ "bat": 2 })
, loop over source:
-
source["apple"]
is not equal to item["apple"]
(latter is undefined)
- return false
So for currentItemIsAMatch({ "apple": 1, "bat": 2, "cookie": 2 })
, loop over source:
-
source["apple"]
is equal to item["apple"]
- continue
-
source["bat"]
is equal to item["bat"]
- continue
- loop has finished, return true
So the callback returns true for the first and third items:
collection.filter(currentItemIsAMatch)
Will resolve to [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }]
NOTE that this is only one way of doing it, there are many other approache that you can use (and should investigate). For example, it could be similar to above, but use Object.keys
and the Array.prototype.every
method.