Home > Blockchain >  Filter array and compare but skip null values
Filter array and compare but skip null values

Time:11-16

I am currently trying to filter available products based on their selected options.

const products = [{
        id: 1,
        name: 'Safari',
        horsepowers: 30,
        doors: 4,
        gear_type: 'automatic',
        wheels: 6
    },
    {
        id: 2,
        name: 'Jungle',
        horsepowers: 50,
        doors: 3,
        gear_type: 'automatic',
        wheels: 5
    },
    {
        id: 3,
        name: 'Moon',
        horsepowers: 30,
        doors: 4,
        gear_type: 'manual',
        wheels: 4
    }
]
const selectedOptions = 
{
   horsepowers: 50,
   doors: 3,
   gear_type: null,
   wheels: null
}

Typically I would do something like

const availableProducts = products.filter((product) => 
  product.horsepowers === selectedOptions.horsepowers &&
  product.doors === selectedOptions.doors .... etc

however, how do I skip null values, empty arrays, and undefined values if the user has not yet selected all possible options yet?

CodePudding user response:

The next provided approach takes advantage of the 2nd thisArg argument of almost every available prototypal array method.

Thus one can write a generic filter function which compares any item's property values to the related ones configured by the selectedOptions object which will be passed alongside the filter function as filter's 2nd argument and as the filter function's this context ...

const selectedOptions = {
  horsepowers: 50,
  doors: 3,
  gear_type: null,
  wheels: null,
};
const products = [{
  id: 1,
  name: 'Safari',
  horsepowers: 30,
  doors: 4,
  gear_type: 'automatic',
  wheels: 6,
}, {
  id: 2,
  name: 'Jungle',
  horsepowers: 50,
  doors: 3,
  gear_type: 'automatic',
  wheels: 5,
}, {
  id: 3,
  name: 'Moon',
  horsepowers: 30,
  doors: 4,
  gear_type: 'manual',
  wheels: 4,
}];

function doItemPropertiesEqualEveryBoundSelectedOption(item) {
  return Object
    // create key value pairs from the `this` bound selected options.
    .entries(this)
    // skip/ignore selected option entries where `value` equals `null`.
    .filter(([key, value]) => value !== null)
    // execute item specific selected option validation via `every`.
    .every(([key, value]) => item[key] === value);    
}
console.log(
  products
    .filter(
      doItemPropertiesEqualEveryBoundSelectedOption,
      selectedOptions,
    )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

In order to answer another of the OP's questions ...

"however, how do I skip null values, empty arrays, and undefined values if the user has not yet selected all possible options yet?"

... and also provide a generic solution to it, the above approach can be changed to a thisArg object which not only features the selected options but also the condition of not to be validated (invalid) selectedOption properties ...

const products = [{
  id: 1,
  name: 'Safari',
  horsepowers: 30,
  doors: 4,
  gear_type: 'automatic',
  wheels: 6,
}, {
  id: 2,
  name: 'Jungle',
  horsepowers: 50,
  doors: 3,
  gear_type: 'automatic',
  wheels: 5,
}, {
  id: 3,
  name: 'Moon',
  horsepowers: 30,
  doors: 4,
  gear_type: 'manual',
  wheels: 4,
}];

const selectedOptions = {
  horsepowers: 50,
  doors: 3,
  gear_type: null,
  wheels: null,
};
const isInvalidValue = (value) => {
  return Array.isArray(value)
    // empty array validation.
    ? (value.length === 0)
    // undefined and null value validation.
    : (value == null)
}

function doItemPropertiesEqualEveryBoundValidOption(item) {
  const { options, isInvalidValue } = this;
  return Object
    .entries(options)
    .filter(([key, value]) => !isInvalidValue(value))
    .every(([key, value]) => item[key] === value);    
}
console.log(
  products
    .filter(
      doItemPropertiesEqualEveryBoundValidOption,
      { options: selectedOptions, isInvalidValue },
    )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

selectedOptions.horsepowers == null ? true : product.horsepowers === selectedOptions.horsepowers &&
product.doors == null ? true : product.doors === selectedOptions.doors

If you want to keep it in line, you can use a ternary operator to check if it's null before comparing.

CodePudding user response:

You could preprocess selectedOptions first and then comparing:

const applicableOptions = Object.entries(selectedOptions).filter(
  ([_, value]) => value !== null && value !== undefined
);

const availableProducts = products.filter((product) =>
  applicableOptions.every(([optKey, optValue]) => product[optKey] === optValue)
);

to compare using array, you would need to update your example as there's no array property

CodePudding user response:

Rather than typing in each option by hand, you could just iterate over selectedOptions. Then it's as simple as checking if the value of each option is null before comparing.

let filtered = products.filter(e => {
  for(let [key, value] of Object.entries(selectedOptions))
  {
    if(value != null && e[key] != value)
      return false;
  }
  return true;
});

Show code snippet

const products = [{
        id: 1,
        name: 'Safari',
        horsepowers: 30,
        doors: 4,
        gear_type: 'automatic',
        wheels: 6
    },
    {
        id: 2,
        name: 'Jungle',
        horsepowers: 50,
        doors: 3,
        gear_type: 'automatic',
        wheels: 5
    },
    {
        id: 3,
        name: 'Moon',
        horsepowers: 30,
        doors: 4,
        gear_type: 'manual',
        wheels: 4
    }
]

const selectedOptions = 
{
   horsepowers: 50,
   doors: 3,
   gear_type: null,
   wheels: null
}

let filtered = products.filter(e => {
  for(let [key, value] of Object.entries(selectedOptions))
  {
    if(value != null && e[key] != value)
      return false;
  }
  return true;
});

console.log(filtered);
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

However, if you really want to write it out, I'd just check if the option is set with a simple boolean check. !(null) returns true, so this would work.

return (!selectedOptions.horsepowers || selectedOptions.horsepowers == product.horsepowers) && ...

CodePudding user response:

You could generate a filter array from selectedOptions and filter the entries and use this for filtering the data, later.

const
    products = [{ id: 1, name: 'Safari', horsepowers: 30, doors: 4, gear_type: 'automatic', wheels: 6 }, { id: 2, name: 'Jungle', horsepowers: 50, doors: 3, gear_type: 'automatic', wheels: 5 }, { id: 3, name: 'Moon', horsepowers: 30, doors: 4, gear_type: 'manual', wheels: 4 }],
    selectedOptions = { horsepowers: 50, doors: 3, gear_type: null, wheels: null },
    filter = Object
        .entries(selectedOptions)
        .filter(([, v]) => v !== null),
    result = products.filter(o => filter.every(([k, v]) => o[k] === v));

console.log(result);
    
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

  • Using Object#entries and Array#filter, get the pairs with selected values from selectedOptions to use for filtering the products list
  • Using Array#filter and Array#every, filter the list to make sure that resulting products match the above pairs

const 
  products = [ { id: 1, name: 'Safari', horsepowers: 30, doors: 4, gear_type: 'automatic', wheels: 6 }, { id: 2, name: 'Jungle', horsepowers: 50, doors: 3, gear_type: 'automatic', wheels: 5 }, { id: 3, name: 'Moon', horsepowers: 30, doors: 4, gear_type: 'manual', wheels: 4 } ],
  selectedOptions = { horsepowers: 50, doors: 3, gear_type: null, wheels: null };

const filterOptions = 
  Object.entries(selectedOptions).filter(([_, value]) => value !== null);
const selectedProducts = 
  products.filter(product =>
    filterOptions.every(([key, value]) => product[key] === value)
  );

console.log(selectedProducts);
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related