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