Jamie Curnow
Jamie Curnow

Reputation: 794

How to use filter to search in multiple key values of objects in an array?

I have an array of wines containing objects with data for each wine:

var wines = [
  { _id: '59a740b8aa06e549918b1fda',
    wineryName: 'Some Winery',
    wineName: 'Pinot Noir',
    wineColor: 'Red',
    imageLink: '/img/FortBerensPN.png' },
  { _id: '59a7410aaa06e549918b1fdb',
    wineryName: 'Some Winery',
    wineName: 'Pinot Gris',
    wineColor: 'White',
    imageLink: '/img/FortBerensPG.png' },
  { _id: '59a74125aa06e549918b1fdc',
    wineryName: 'Some Winery',
    wineName: 'Rose',
    wineColor: 'Rose',
    imageLink: '/img/FortBerensRose.png' },
  { _id: '59a74159aa06e549918b1fdd',
    wineryName: 'Some other Winery',
    wineName: 'Rose',
    wineColor: 'Rose',
    imageLink: '/img/FortBerensRose.png' },
  { _id: '59a7417aaa06e549918b1fde',
    wineryName: 'Some other Winery',
    wineName: 'Pinot Gris',
    wineColor: 'White',
    imageLink: '/img/FortBerensPG.png' },
  { _id: '59a8721f4fd43b676a1f5f0d',
    wineryName: 'Some other Winery',
    wineName: 'Pinot Gris',
    wineColor: 'White',
    imageLink: '/img/FortBerensPG.png' },
  { _id: '59a872244fd43b676a1f5f0e',
    wineryName: 'Winery 3',
    wineName: 'Pinot Noir',
    wineColor: 'Red',
    imageLink: '/img/FortBerensPN.png' } ]

I can figure out how to search -- case insensitive -- for a wine object, while specifying which key of the object to search in, like this:

var search = 'Noir'

filteredWines = function () {
  return wines.filter(function(wine){
    return (wine.wineName.toLowerCase().indexOf(search.toLowerCase())>=0;
  });
};

Returns:

[ { _id: '59a740b8aa06e549918b1fda',
    wineryName: 'Some Winery',
    wineName: 'Pinot Noir',
    wineColor: 'Red',
    imageLink: '/img/FortBerensPN.png' },
  { _id: '59a872244fd43b676a1f5f0e',
    wineryName: 'Winery 3',
    wineName: 'Pinot Noir',
    wineColor: 'Red',
    imageLink: '/img/FortBerensPN.png' } ]

However, if var search = 'Winery 3' or var search = 'red' then it will obviously return no results, as it's looking in the value of wineName of each object in the array.

So is there a way to use filter (or another method?) to search through all key values, or even better, multiple specified key values and return an array of the matching objects?

Something like:

filteredWines = function () {
  return wines.filter(function(wine){
    return ((wine.wineName.toLowerCase() && wine.wineName.toLowerCase() 
          && wine.wineName.toLowerCase()).indexOf(search.toLowerCase())>=0;
  });
};

Or am I completely barking up the wrong tree?

PS. I'm using Vue.js 2 so if there's a better way inside vue then I'm all ears!

Upvotes: 5

Views: 10729

Answers (5)

trincot
trincot

Reputation: 350054

You could have a more generic function that will scan all the properties for the string. Loop through all property values with Object.values() and use some to bail out as soon as you have a match:

filteredWines = function (search) {
    var lowSearch = search.toLowerCase();
    return wines.filter(wine =>
        Object.values(wine).some(val => 
            String(val).toLowerCase().includes(lowSearch) 
        )
    );
}

If you prefer to pass specific keys to search in:

filteredWines = function (search, keys) {
    var lowSearch = search.toLowerCase();
    return wines.filter(wine =>
        keys.some(key => 
            String(wine[key]).toLowerCase().includes(lowSearch) 
        )
    );
}

Call as

filteredWines('Winery 3', ['wineryName', 'wineName']);

Upvotes: 23

sreimers
sreimers

Reputation: 13

Used the solution from "trincot" and changed it for my angular5 application to this:

filter(search, list): Observable<IFilteredList> {
  return list.filter(item => {
    return Object.values(item).some(val =>
      String(val).includes(search)
    );
  })
}

Upvotes: 1

Diogo Rodrigues
Diogo Rodrigues

Reputation: 1332

Can also be done this way:

    this.wines = this.wines.filter((item) => {
                return (item.wineryName.toString().toLowerCase().indexOf(val.toLowerCase()) > -1 ||
                item.wineName.toLowerCase().indexOf(val.toLowerCase()) > -1 ||
                item.wineColor.toLowerCase().indexOf(val.toLowerCase()) > -1);
            })

Upvotes: 1

jonahe
jonahe

Reputation: 5000

I also would recommend trying a more general approach:

function getMatchingWhine(keys, searchTerm, wines) {

  function extractTextFromKeys(keys, object) {
    let text = '';
    keys.forEach(key => {
        text += ' ' + object[key];
    });
    return text.toLowerCase();
  }

  return wines.filter(wine => {
    const relevantText = extractTextFromKeys(keys, wine);
    return relevantText.includes(searchTerm.toLowerCase());
  });
}

Upvotes: 0

Will
Will

Reputation: 3241

Filter works. Oops, I read the question a bit more closely. Filter still works but you have to filter the values, too.

let wines = [
    {
        _id: '59a740b8aa06e549918b1fda',
        wineryName: 'Some Winery',
        wineName: 'Pinot Noir',
        wineColor: 'Red',
        imageLink: '/img/FortBerensPN.png'
    },
    {
        _id: '59a7410aaa06e549918b1fdb',
        wineryName: 'Some Winery',
        wineName: 'Pinot Gris',
        wineColor: 'White',
        imageLink: '/img/FortBerensPG.png'
    },
    {
        _id: '59a74125aa06e549918b1fdc',
        wineryName: 'Some Winery',
        wineName: 'Rose',
        wineColor: 'Rose',
        imageLink: '/img/FortBerensRose.png'
    },
    {
        _id: '59a74159aa06e549918b1fdd',
        wineryName: 'Some other Winery',
        wineName: 'Rose',
        wineColor: 'Rose',
        imageLink: '/img/FortBerensRose.png'
    },
    {
        _id: '59a7417aaa06e549918b1fde',
        wineryName: 'Some other Winery',
        wineName: 'Pinot Gris',
        wineColor: 'White',
        imageLink: '/img/FortBerensPG.png'
    },
    {
        _id: '59a8721f4fd43b676a1f5f0d',
        wineryName: 'Some other Winery',
        wineName: 'Pinot Gris',
        wineColor: 'White',
        imageLink: '/img/FortBerensPG.png'
    },
    {
        _id: '59a872244fd43b676a1f5f0e',
        wineryName: 'Winery 3',
        wineName: 'Pinot Noir',
        wineColor: 'Red',
        imageLink: '/img/FortBerensPN.png'
    }
];

let search = (val) => wines.filter(w => Object.values(w).filter(v => v.toLowerCase().indexOf(val.toLowerCase()) !== -1).length > 0);

console.log(search('some'));

Upvotes: 0

Related Questions