So, struggling a little with this one.
I have an object which looks like this:
const search = something.map((some) => {
const search = {
writer: some.writers, // (this is an array)
title: some.title.toLowerCase(),
reference: some.reference.toLowerCase(),
};
return search;
});
I am trying to do a search on all the text inside the values of the object.
The some.writer
field is an array that can have numerous writers inside.
I am currently filtering through like this:
const filtered = search.filter((entry) => Object.values(entry).some((val) => typeof val === 'string'
&& val.includes(searchTerm)));
That filter looks for the search term inside the object and returns all strings which contain the term or part of the term.
The problem is, it only looks at the top level and does not go inside the writers
array to search inside there too.
How can I do that?
CodePudding user response:
See below for an option where you don't need the search
array (provided you're doing full-string matches, not substrings), but if you keep the search
array then When building it from the something
array, I'd also make all the writers lower case (and I'd use toLocaleLowerCase
for locale-awareness in the "to lower" operation):
const search = something.map((some) => {
const entry = {
writer: some.writers.map(writer => writer.toLocaleLowerCase()),
title: some.title.toLocaleLowerCase(),
reference: some.reference.toLocaleLowerCase(),
};
return entry;
});
Then when filtering, instead of building a bunch of intermediate arrays, I'd search more directly:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(({title, reference, writers}) => {
return title === val || reference === val || writers.includes(val);
});
But if you want to make it dynamic so adding new properties to the entries works without modifying the search code (other than when you create search
), then as has been pointed out in comments, you can use flat
to flatten the array of values so writers
is spread out into the top level of the array:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(entry => Object.values(entry).flat().includes(val));
Both are fairly simple to tweak to make substring searches:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(({title, reference, writers}) => {
return title.includes(val) || reference.includes(val) || writers.some(writer => writer.includes(val));
});
and
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(entry => Object.values(entry).flat().some(e => e.includes(val)));
You might also want to use normalize
on the search values and val
to handle discrepancies in the different ways some "characters" can be represented in Unicode. If so, basically change all the .toLocaleLowerCase()
above to .normalize().toLocaleLowerCase()
.
If you're doing exact matches, you can avoid having to create the search
array and doing all those toLowerCase
/toLocaleLowerCase
calls. This also has the advantage that the search results have the text in its original form. To do that you'd use an Intl.Collator
object telling it that you want case-insensitive matching via the sensitivity
option:
const {compare} = new Intl.Collator(undefined, {
sensitivity: "accent" // Or you might want `"base"`
});
const match = (a, b) => compare(a, b) === 0);
const filtered = something.filter(({title, reference, writers}) => {
return match(title, val) || match(reference, val) || writers.some(writer => match(writer, val));
});
CodePudding user response:
You can use Array.prototype.flat
to turn something like [1, [2, 3], 4]
into [1, 2, 3, 4]
:
const filtered = search.filter((entry) => Object.values(entry).flat().some((val) => typeof val === 'string' && val.includes(searchTerm)));
// ^^^^^^^