Sorting through an array of objects using criteria from a separate array of objects

Currently, I am faced with the task of filtering an array of objects based on criteria from another array. Essentially, I am looking to refine a list based on filters selected by the user.

To illustrate, let's consider my initial list of results:

[
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5}
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5}
 {uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5}
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5}
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5}
....
]

The filter list that I need to apply is dynamic, meaning it can contain various key-value pairs chosen by the user. This filter list might look like this:

[
 {key: 'type', value: '2'},
 {key: 'style', value: '4'}
]

My goal is to generate a filtered list that only includes values matching all the specified key-value pairs.

I've explored multiple solutions on Stack Overflow but haven't had success yet. Some resources I've referred to include:

How to check if a value exists in an object using JavaScript

How to efficiently check if a Key Value pair exists in a Javascript "dictionary" object

lodash: filter array of objects with a different array of objects

Filtering for Multiple Fields in Object Array in Javascript/Lodash - Stack Overflow

Despite these references, I'm yet to find a solution that meets my requirements.

Here is a snippet of what I've attempted so far:

...
// In this section, I convert the filters into an array format {key: '', value: ''} as mentioned earlier. This step is not mandatory, and I can remove it if necessary.
// Initially, my filters are in this format: {type: 2, style: 4}

const filterArr = Object.keys(cleanedFilters).map(key => ({ key, value: cleanedFilters[key] }))



const result = flatten(
  map(filterArr, function (fil) {
    return filter(searchResults, function (a) {
          return a.hasOwnProperty(fil.key) && a[fil.key] === parseInt(fil.value)        
    })
  })
)

The output of this code block produces an array containing certain objects:

[
    {
        "uuid": 1,
        "type": 2,
        "style": 3,
        "somethingelse": "4",
        "anotherval": 5
    },
    {
        "uuid": 2,
        "type": 4,
        "style": 4,
        "somethingelse": "4",
        "anotherval": 5
    },
    {
        "uuid": 3,
        "type": 6,
        "style": 4,
        "somethingelse": "4",
        "anotherval": 5
    }
]

This outcome includes items satisfying both style = 4 and type = 2. However, my desired result is an empty array since there are no entries with style 4 and type 2.

As shown in the provided example, I am open to utilizing lodash or similar libraries if necessary.

I appreciate any suggestions or insights you may have. Thank you in advance.

Answer №1

To effectively filter the array, ensure that all items in the filter match with the values in the array. However, a challenge arises when the filter contains string values while the array holds numerical ones. There are multiple approaches to handle this issue; select the one most suitable for your requirements.

const arr = [
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
];

const filter = [
 {key: 'type', value: '2'},
 {key: 'style', value: '4'}
];

const filtered = arr.filter(item => filter.every(({key, value}) => item[key] === +value));
 
console.log(filtered);

If your filter is represented initially as an object, it can also be implemented like this:

const arr = [
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
];

const filter = {type: '2', style:'4'};

const filtered = arr.filter(item => Object.entries(filter).every(([key, value]) => item[key] === +value));
 
console.log(filtered);

For optimal performance, especially when dealing with large arrays from backend operations, you can create a filter function:

const arr = [
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
];

const filter = {type: '2', style:'4'};

const filterFn = new Function('item', 'return ' + Object.entries(filter).map(([key, val]) => `item.${key} === ${val}`).join(' && '));     

console.log('filterFn:', filterFn);

const filtered = arr.filter(filterFn);    

console.log(filtered);

Conduct a benchmark comparison:

https://i.sstatic.net/SxerW.png

<script benchmark data-count="10">

const arr = [];

const chunk = [
 {uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5},
 {uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 3, type: 2, style: 4, somethingelse: 4, anotherval: 5},
 {uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5},
 {uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5},
];

let count = 1000000;
while(count--){
  arr.push(...chunk);
}

const filter = {type: '2', style:'4'};
const filterFn = new Function('item', 'return ' + Object.entries(filter).map(([key, val]) => `item.${key} === ${val}`).join(' && '));


// @benchmark Array.every

const filterArr = Object.entries(filter).map(([key, value]) => ({key, value: +value}));
arr.filter(item => filterArr.every(({key, value}) => item[key] === value));

// @benchmark Function generation
arr.filter(filterFn);

 
</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>

Answer №2

While looping through the objects within the data array, utilize the every method to verify that each key/value pair in the query meets the condition of existing in the current object being iterated.

const data = [
 {id: 1, type: 2, style: 3, additionalInfo: 4, anotherValue: 5},
 {id: 2, type: 4, style: 4, additionalInfo: 4, anotherValue: 5},
 {id: 3, type: 6, style: 4, additionalInfo: 4, anotherValue: 5},
 {id: 4, type: 9, style: 2, additionalInfo: 4, anotherValue: 5},
 {id: 5, type: 1, style: 2, additionalInfo: 4, anotherValue: 5}
];

const query = [
 {key: 'type', value: '2'},
 {key: 'style', value: '4'}
];

const query2 = [
 {key: 'type', value: '2'},
 {key: 'style', value: '3'}
];

function filterData(data, query) {
  return data.filter(obj => {
    
    // Return true if every key/value pair in the
    // current iterated query object exists in the
    // current iterated data object
    // otherwise return false
    return query.every(q => {
      return obj[q.key] === +q.value;
    });
  });
}

console.log(filterData(data, query));
console.log(filterData(data, query2));

Answer №3

If you want to ensure that the values inputted are valid integers, make sure to validate the user input within the parseFilter function below. To filter based on the intersection of all filter values (using boolean AND), you can utilize Array.prototype.every():

TS Playground

function parseFilter(
  input: Readonly<Record<"key" | "value", string>>
): { key: string; value: number; } {
  const value = Number(input.value);
 
  if (
    !(
      input.value
      && Number.isInteger(value)
    )
  ) throw new Error(`Invalid filter value: ${input.value}`);
  return { key: input.key, value };
}

const input: Record<string, number>[] = [
  { uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5 },
  { uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5 },
  { uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5 },
  { uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5 },
  { uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5 },
];

const filters = [
  { key: "type", value: "2" },
  { key: "style", value: "4" },
];

const parsed = filters.map(parseFilter);

const filtered = input.filter((o) =>
  parsed.every(({ key, value }) => o[key] === value)
);

console.log(filtered);

Compiled JS:

"use strict";
function parseFilter(input) {
    const value = Number(input.value);
  
    if (!(input.value
        && Number.isInteger(value)))
        throw new Error(`Invalid filter value: ${input.value}`);
    return { key: input.key, value };
}
const input = [
    { uuid: 1, type: 2, style: 3, somethingelse: 4, anotherval: 5 },
    { uuid: 2, type: 4, style: 4, somethingelse: 4, anotherval: 5 },
    { uuid: 3, type: 6, style: 4, somethingelse: 4, anotherval: 5 },
    { uuid: 4, type: 9, style: 2, somethingelse: 4, anotherval: 5 },
    { uuid: 5, type: 1, style: 2, somethingelse: 4, anotherval: 5 },
];
const filters = [
    { key: "type", value: "2" },
    { key: "style", value: "4" },
];
const parsed = filters.map(parseFilter);
const filtered = input.filter((o) => parsed.every(({ key, value }) => o[key] === value));
console.log(filtered);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What strategies can I implement to integrate Cordova with a combination of Meteor and React?

I'm currently struggling to implement a Cordova plugin with Meteor and React. According to the documentation: You should wrap any functionality that relies on a Cordova plugin inside a Meteor.startup() block to ensure that the plugin has been fully ...

Delete the initial image from the opening list item using jQuery

Here is an example of some HTML code: <ul class="products"> <li> <a href="#" class="product-images"> <span class="featured-image"> <img src="img1.jpg"/> <img src="img ...

How do I trigger a click event on an autocomplete search bar in Vue with a router link enabled?

I'm working on an app that is similar to Github, and I want to create a search bar functionality just like Github's. However, I'm facing an issue with my search bar located in the navbar option section as it doesn't seem to register any ...

Invoking vscode Extension to retrieve data from webview

One task I'm currently working on involves returning a list from the extension to be displayed in the input box of my webview page. The idea is for a JavaScript event within the webview to trigger the extension, receive the list object, and then rend ...

Storing items in localStorage in the same order they were added

I am having an issue with sorting localStorage items by the order they were added. Despite my code working to add items to the localStorage array and display them as HTML, I am encountering a problem with the order of the items. When I add a 3rd item to t ...

Encountering a post route error when utilizing async await has hindered my ability to add a new product

Recently, I attempted to update my post route using async await, and unfortunately made some mistakes. Now I'm unsure how to correct it properly. router.post('/', async (req, res, next)=> { try{ const updatedProduct = await ...

What is the best way to update the content of a particular div and its associated link ID whenever the link is clicked?

I need to update the content of a div by clicking on an href link that includes an id named data. The issue I'm facing is that I can't access the id value within my script because it's passed within a function. How can I pass the data variab ...

"Utilizing a Handlebars Helper to Evaluate if Two Values (v1 and v2) are Equal, and Displaying Content from

To make the actual call, I require something along these lines: <script id="messagesTemplate" type="text/x-handlebars-template"> {{#each messages.messages}} {{#each to}} {{#ifCond username messages.sessionUserName}} <h1> ...

Using jQuery to dynamically add a value to a comment string

Is there a way to dynamically include tomorrow's start and end times in the message for the setupOrderingNotAvailable function if today's end time has passed? The current message states that online ordering will be available again tomorrow from 1 ...

Error when spaces are present in the formatted JSON result within the link parameter of the 'load' function in JQuery

I am facing an issue with JSON and jQuery. My goal is to send a formatted JSON result within the link using the .load() function. <?php $array = array( "test1" => "Some_text_without_space", "test2" => "Some text with space" ); $json = jso ...

Encountering an issue with usememo in React js?

I'm currently experimenting with the useMemo hook in React JS. The goal is to sort an array of strings within a function. However, when I return the array from the function, only the first element is being returned. Can someone please assist me in ide ...

Guide to creating a new browser tab in Java Script with customizable parameters

Here is a function I created to open a new window with a specific URL and pass certain parameters. function viewWeightAge() { var userNIC = document.getElementById("NICNo"); var childName = document.getElementById("c ...

What is the reason for the reduction in dependency versions when installing npm

Encountering an issue with installing a package using npm. The process is resulting in decreased dependencies versions, which is causing issues with my application and unit tests. For example, after installation, my package.lock file looks like this: htt ...

Searching for specific data within an embedded documents array in MongoDB using ID

While working with mongodb and nodejs to search for data within an embedded document, I encountered a strange issue. The query functions as expected in the database but not when implemented in the actual nodejs code. Below is the structure of my data obje ...

What is the best way to add a new item to an object using its index value?

Can the Locations object have a new property added to it? The property to be added is: 2:{ name: "Japan", lat: 36, lng: 138, description: 'default', color: 'default', url: 'default' } The current Location ...

Displaying the information fetched using axios on the screen using node.js

Is there a way to display the information from the input boxes in the image on the screen using node js? ...

Explore the hidden route of the input components

HTML: <div id="quiz"> <div id="question"> <p id="quiz-txt">What is your favorite color?</p> <ul id="quiz-opt"> <div id="ans"> <input type="checkbox" id="Red" value="Red" class="options"> ...

React classes with external scripts

I'm currently working on embedding an external map service into my React application. The recommended way to integrate the API into a regular HTML web page is shown below: <script type="text/javascript" src="//map.search.ch/api/map.j ...

The Makearray tool in Microsoft Excel is struggling to generate the correct number of columns for the array

I am currently utilizing Office 365 and I'm looking to create visualization tools using the MAKEARRAY functions. For instance, if I want to show a sequence of 32 items, it would look like this: https://i.sstatic.net/z3WeF.png I am using the following ...

Tips on showing an api call response in Reactjs

I am a beginner in Reactjs and currently working with nextjs. I have been trying to integrate a "newsletter" feature into my project. I have created a component for this which is functioning properly. However, I am facing an issue with displaying the "succ ...