Home > Blockchain >  Filter on an object with an array inside
Filter on an object with an array inside

Time:10-13

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)));
//                                                            ^^^^^^^
  • Related