marielle
marielle

Reputation: 438

Filter array of objects with available or not filters

Suppose I have this dataset:

const data = [ 
  {animal: 'cat', name: 'mu', year: 2016},
  {animal: 'cat', name: 'muji', year: 2021},
  {animal: 'cat', name: 'mine', year: 2021},
  {animal: 'dog', name: 'fido', year: 2000}, 
  {animal: 'hamster', name: 'gerry', year: 2020}, 
  {animal: 't-rex', name: 'dino', year: 2020}, 
  {animal: 'sheep', name: 's', year: 2019}, 
  {animal: 'sheep', name: 'sss', year: 2016}, 
]

and I want to filter it by some values, for example by animal and by year. Very easy I can do:

const animal = 'sheep'
const year = 2019

const filteredData = data.filter(d => d.animal === animal && d.year === year)
// result
const filteredData = [ 
  {animal: 'sheep', name: 's', year: 2019}, 
]

same here:

const animal = 'cat'
const year = 2021
const filteredData = [ 
  {animal: 'cat', name: 'muji', year: 2021},
  {animal: 'cat', name: 'mine', year: 2021},
]

My problem is that sometimes animal or year can be null. In that case I want not to filter by that value. For example these are what I would like to obtain:

const animal = 'cat'
const year = null
const filteredData = [ 
  {animal: 'cat', name: 'mu', year: 2016},
  {animal: 'cat', name: 'muji', year: 2021},
  {animal: 'cat', name: 'mine', year: 2021},
]

// ---

const animal = null
const year = 2020
const filteredData = [ 
  {animal: 'hamster', name: 'gerry', year: 2020}, 
  {animal: 't-rex', name: 'dino', year: 2020},
]

// ---

const animal = null
const year = null
const filteredData = [ 
  {animal: 'cat', name: 'mu', year: 2016},
  {animal: 'cat', name: 'muji', year: 2021},
  {animal: 'cat', name: 'mine', year: 2021},
  {animal: 'dog', name: 'fido', year: 2000}, 
  {animal: 'hamster', name: 'gerry', year: 2020}, 
  {animal: 't-rex', name: 'dino', year: 2020}, 
  {animal: 'sheep', name: 's', year: 2019}, 
  {animal: 'sheep', name: 'sss', year: 2016}, 
]

How can I do that?

Note that this is only a simple example, in my case I could have more than two variables as filters.

Upvotes: 1

Views: 229

Answers (3)

connexo
connexo

Reputation: 56753

This would be my first shot at the problem (see comments in code):

const data = [{animal: 'cat', name: 'mu', year: 2016}, {animal: 'cat', name: 'muji', year: 2021}, {animal: 'cat', name: 'mine', year: 2021}, {animal: 'dog', name: 'fido', year: 2000},  {animal: 'hamster', name: 'gerry', year: 2020},  {animal: 't-rex', name: 'dino', year: 2020},  {animal: 'sheep', name: 's', year: 2019},  {animal: 'sheep', name: 'sss', year: 2016}];

// Instead of using filter literals, use a filter object:
const filter = { animal: 'dog',  year: null };

// Then, for filtering, write your own filter function:
const filterAnimals = (arr, filters) => arr
    .filter(animal => Object.entries(filters)
      .filter(([key, value]) => value !== null) // removes filter properties equaling null
      .every(([key, value]) => animal[key] === value));

console.log(filterAnimals(data, filter));

Upvotes: 0

brc-dd
brc-dd

Reputation: 13004

As you have written "I could have more than two variables as filters…", I don't think it is a good idea to hard-code filter expressions. Moreover, if both your data and filter are dynamic (if properties are not known beforehand) it is not even possible. Instead, you can use some approach like this:

const data = [
  { animal: 'cat', name: 'mu', year: 2016 },
  { animal: 'cat', name: 'muji', year: 2021 },
  { animal: 'cat', name: 'mine', year: 2021 },
  { animal: 'dog', name: 'fido', year: 2000 },
  { animal: 'hamster', name: 'gerry', year: 2020 },
  { animal: 't-rex', name: 'dino', year: 2020 },
  { animal: 'sheep', name: 's', year: 2019 },
  { animal: 'sheep', name: 'sss', year: 2016 }
];

const query = { animal: 'cat', year: null };

// remove all null values
// https://stackoverflow.com/a/38340730
const q = Object.fromEntries(Object.entries(query).filter(([_, v]) => v != null));

// https://stackoverflow.com/a/61676007
const isSubset = (superObj, subObj) => {
  return Object.keys(subObj).every(ele => {
    if (typeof subObj[ele] == 'object') return isSubset(superObj[ele], subObj[ele]);
    return subObj[ele] === superObj[ele];
  });
};

const filteredData = data.filter(d => isSubset(d, q));

console.log(filteredData);

Upvotes: 1

DecPK
DecPK

Reputation: 25408

You can use Nullish coalescing operator and use filters as

d.animal === (animal ?? d.animal)

const data = [
  { animal: "cat", name: "mu", year: 2016 },
  { animal: "cat", name: "muji", year: 2021 },
  { animal: "cat", name: "mine", year: 2021 },
  { animal: "dog", name: "fido", year: 2000 },
  { animal: "hamster", name: "gerry", year: 2020 },
  { animal: "t-rex", name: "dino", year: 2020 },
  { animal: "sheep", name: "s", year: 2019 },
  { animal: "sheep", name: "sss", year: 2016 },
];

const animal = null;
const year = null;

const filteredData = data.filter(
  (d) => d.animal === (animal ?? d.animal) && d.year === (year ?? d.year)
);

console.log(filteredData);

Upvotes: 2

Related Questions