pengz
pengz

Reputation: 2471

Javascript filter array of objects skip empty string

I am building a REST API in NodeJS. I am building the server-side pagination, sorting and filtering.

I have a simple implementation of the filter method. This works if the item does not contain any empty strings. However if the item contains an empty string, the .toString() method fails because item[filterBy] is null.

Cannot read property 'toString' of null

How can I update the filter function to simply return true if the item contains an empty string, to skip the comparison operations?

  // Apply filter to array of objects
  // TODO: Empty strings breaks the filter
  items = items.filter(item =>
    item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)

Upvotes: 2

Views: 3376

Answers (5)

zer00ne
zer00ne

Reputation: 44086

flatMap() is .map() and .flat() combined. It can act like a reverse .filter() by directly returning or not returning values. See demo for details.

const items = [{
  id: 'a',
  pn: 'B',
  desc: ''
}, {
  id: 'b',
  pn: 'c',
  desc: 'd'
}, {
  id: 'c',
  pn: 'k',
  desc: null
}, {
  id: 'i',
  pn: 'k',
  desc: 2
},, {
  id: 'o',
  pn: 'x',
  desc: 3
}];

// Utility function that returns a 'friendlier' value
function clean(obj) {
  return obj == null ? false : Array.isArray(obj) ? obj : obj.constructor.name.toLowerCase() === "object" ? obj.toString().trim().toLowerCase() : typeof obj === "string" ? obj.toString().trim().toLowerCase() : Number(parseFloat(obj)) === obj ? obj : false;
}

// Filters by a single key and multiple values
let filterKey = clean('desc');
let filterVal = clean(['d', 2, 'f']);

/* It returns each value in a sub-array an empty array will remove
the value. In the final stage it flattens the array of arrays into a normal array
*/
let list = items.flatMap(item => {
  return Object.entries(item).flatMap(([key, value]) => filterKey === (clean(key)) && [...filterVal].includes(clean(value)) ? [clean(value)] :[]);
});

console.log(list);

Upvotes: 1

Alex Pakka
Alex Pakka

Reputation: 9706

It is correct to chain filters and it does not affect performance.

const filterValue = 'John'
const filterBy = 'name'
const items = [
  {id: 1, name: 'John'},
  {id: 2, name: 'Doe, John'},
  {id: 3, ref: 1},
  {id: 4, name: 'No one'},
  {id: 5, name: null}
]

let fItems = items.filter(item => item[filterBy])
                  .filter(item => item[filterBy].toString().trim().toLowerCase()
                     .indexOf(filterValue.toLowerCase()) !== -1)
   
console.log(fItems);

Update: fixed the code.

Upvotes: 0

Scott Sauyet
Scott Sauyet

Reputation: 50807

Perhaps something like this:

const filterValue = 'abc'
const filterBy = 'name'
const items = [
  {x: 1, name: 'abc'},
  {x: 2, name: 'def'},
  {x: 3, noname: 'def'},
  {x: 4, name: 'ABCDEF'},
]

const newItems = items.filter(item =>
    !(filterBy in item) || item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)
    
console.log(newItems)

All we do here is check if the item has a filterBy property.

It's not quite clear to me what the issue is with empty strings. It seems most likely that you would get that error if item[filterBy] is undefined. That's what we check here.

If instead you want to skip those that don't have the relevant property, switch from !(filterBy in item) || ... to (filterBy in item) && ....

Upvotes: 2

Ghoul Ahmed
Ghoul Ahmed

Reputation: 4836

To skip the item contains an empty string, try this:

item[filterBy] && item[filterBy].toString()

So, your code will be:

items = items.filter(item =>
item[filterBy] && item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)

Upvotes: 2

German Burgardt
German Burgardt

Reputation: 395

Maybe:

items = items.filter(
    item => item[filterBy] && item[filterBy].toString ?
        item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1 :
        false
)

Upvotes: 1

Related Questions