2

I want to filter objects based on a set of arrays containing filter terms. It basically works until one of the filter arrays contains multiple terms. Here is the code:

// the filters
const filters = {
	"eyeColor": ["blue"],
	"gender": ["male"],
	"age": ["33"],
	"tags": ["d3js", "vuejs"] // multiple terms in the filter array breaks the code (returns empty array)
}

// the data 
const users = [
	{
		"age": "33",
		"eyeColor": "blue",
		"tags": "d3js, max, grotesk",
		"gender": "male",
	},
	{
		"age": "31",
		"eyeColor": "blue",
		"tags": "vuejs, adrian,  serif",
		"gender": "male",
	},
	{
		"age": "37",
		"eyeColor": "brown",
		"tags": "vuejs, max,  mono, d3js",
		"gender": "female",
	},
	{
		"age": "33",
		"eyeColor": "blue",
		"tags": "vuejs, markus, grotesk",
		"gender": "male",
	},
]

// the filter function
let results = users.filter(function (object) {
	return Object.entries(filters).every(function ([key, value]) {		
		return value.every(function (filter) {
			return object[key].includes(filter)
		})
	})
});
console.log(results);

I get an empty array, while the expected result would be:

{
   "age": "33",
   "eyeColor": "blue",
   "tags": "d3js, max, grotesk",
   "gender": "male",
},
{
    "age": "33",
    "eyeColor": "blue",
    "tags": "vuejs, markus, grotesk",
    "gender": "male",
}

How can I get the expected result?

4
  • 1
    Can you change the format of users so that tags will also be an array of "tags" instead of a string? Commented Dec 18, 2019 at 17:08
  • I guess theoretically this would be possible. However, it's working now! Thank you Commented Dec 19, 2019 at 11:42
  • 1
    I just asked because with this approach the tag serif would also match sans-serif Commented Dec 19, 2019 at 11:54
  • Good thought! Thank you, I will keep that in mind while pursuing the project Commented Dec 19, 2019 at 14:14

1 Answer 1

2

The code uses

return value.every(function (filter) ...
//           ^^^^^

but you don't want to match all of the tags in the permitted array, only some (any):

return value.some(function (filter) ...
//           ^^^^

Here's a demonstration:

const filters = { "eyeColor": ["blue"], "gender": ["male"], "age": ["33"], "tags": ["d3js", "vuejs"] }
const users = [{ "age": "33", "eyeColor": "blue", "tags": "d3js, max, grotesk", "gender": "male", }, { "age": "31", "eyeColor": "blue", "tags": "vuejs, adrian,  serif", "gender": "male", }, { "age": "37", "eyeColor": "brown", "tags": "vuejs, max,  mono, d3js", "gender": "female", }, { "age": "33", "eyeColor": "blue", "tags": "vuejs, markus, grotesk", "gender": "male", }, ];

const filterEntries = Object.entries(filters);
const results = users.filter(user =>
  filterEntries.every(([key, permitted]) =>
    permitted.some(e => user[key].includes(e))
  )
);
console.log(results);

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

4 Comments

Hope you don't mind the edit - started writing an answer but saw no real need to finish once you posted yours, so I just added the runnable snippet.
I noticed that by filtering for "gender": ["male"], it returns also female because it contains "male". In some cases this is great, but would there be a solution to avoid this or is this locically inconsequent?
Right--the problem is that we're using includes which checks substrings. As mentioned above in the comments, it's poor design to have a comma-delimited list--better to use an array or Set of strings and then use has or includes. You can work around this using your current design with a regex that enforces word boundaries, like e => new RegExp(\\b${e}\\b).test(user[key]), or split the string on commas and call includes on that, but that's going to be a performance hit since it's done in a nested loop. Better to pre-process.
Thank you for the clarification! In this case, I will try to follow the advice from above and design the data structure in a way that I have a list of strings rather than checking for substrings. Performance is a key aspect of this development.

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.