MongoDB/Mongoose Query Builder - javascript

I am trying to build a query builder which will allow me to filter the data based on the parameters entered by user. My Data Model is like so:
{
"_id": {
"$oid": "871287215784812"
},
"tags": [
"school",
"book",
"bag",
"headphone",
"appliance"
],
"consultingDays": 57,
"client": "someOne",
"subSector": "something",
"region": "UK",
"__v": 0
}
Currently my Query Builder looks like this:
app.post('/user/test',function(req, res) {
var query = {};
//QUERY NO.1 - This works perfectly
if (req.body.region){
query.region = req.body.region
console.log(query.region)
}
// QUERY NO.2 - This works perfectly
if (req.body.subSector){
query.subSector = req.body.subSector
}
Project.find(query, function(err, project){
if (err){
res.send(err);
}
console.log(project);
res.json(project);
});
});
My Question:
I want to create a query which will take input from user and parse the "tags" array and return the required JSON.
For example:
If the user requests an object which contains "school", "book", "bag" it will return the object as seen my data model above. But if the user requests an object with "school", "book", "ninja Warrior" it won't return any data as no object within the database contain all those 3 strings.
What I have tried:
I have tried the following
if (req.body.sol){
query.solutions = {"tags" : {$in: [req.body.sol]}}
}
OR
if (req.body.sol){
query.solutions = {$elemMatch:{tags: req.body.sol}}
}
OR
if (req.body.sol){
query.solutions = { tags: { $all: [req.body.sol]}}
}
The requests were sent like so and they returned an empty array:
Also the issue is that the user will get dropdown options. For example he/she might get 3 dropdown boxes. Each dropdown box will display all the five options in the tags array. The user will select a value for each dropdown box. And then filter the result. Because there might be an object within the database that contains "book", "bag", "shoes" within the tags array. The user can select any combination of those five keywords in the tags array
Does anyone know how I can fix this?

You need to send an array as sol so in Postman you should change sol with sol[0], sol[1], etc.. Then use this:
if (req.body.sol){
query.solutions = {"tags" : {$in: req.body.sol}}
}
Without the [] because req.body.sol is an array yet.

I have implemented a simple query build for nested objects:
const checkObject = (object) => {
let key;
const status = Object.entries(object).some(([objectKey, objectValue]) => {
if (typeof objectValue === "object" && objectValue !== null) {
key = objectKey;
return true;
}
return false;
});
return { status, key };
};
const queryBuilder = (input) => {
// Array verification not implemented
let output = {};
_.each(input, (value, key) => {
if (typeof value === "object" && value !== null) {
_.each(value, (nestedValue, nestedKey) => {
output[`${[key, nestedKey].join(".")}`] = nestedValue;
});
} else {
output[key] = value;
}
});
const cacheCheckObject = checkObject(output);
if (cacheCheckObject.status)
return { ..._.omit(output, cacheCheckObject.key), ...queryBuilder(output) };
return output;
};
I have not implemented array, but with some small work you can do it work. The same for Mongo operators. The complete example can be seen on Gist.

Related

Find matching items in array of object values from array of filter criteria

I have some data (json) that looks like this:
[
{
"name": "Enterprise Networking",
"technology": "Networking"
},
{
"name": "Enterprise Networking",
"technology": "Networking"
},
{
"name": "Wireless Insights",
"technology": "Mobility and Wireless"
},
{
"name": "Mobility Insights",
"technology": "Mobility and Wireless"
},
{
"name": "Lock it down",
"technology": "Security"
},
{
"name": "Collaboration",
"technology": "Security"
}
]
I am trying to find matches based on an array of filtering options for one field named 'technology' in this example. For example, my filtering criteria will be ["Networking", "Security"]. I would expect to return all objects that have 'Networking' and 'Security' as their technology, excluding 'Mobility and Wireless' in this example.
I have figured out how to do it with just one filter criteria using:
result = learningMapsDataObj.filter(el => el.Technology === filter3[0].value);
I've tried adding a method that will loop through the the filter array, but can't get it to work.
result = learningMapsDataObj.filter(el => el.Technology === myloopingArrCheckFunction(el.technology, filters3));
I know I'm close, but haven't had to dev in a while and my multidimensional brain isn't working.
Approaches
You can check if el.technology is included in your array of values to filter by:
const allowedTechnologies = ["Networking", "Security"];
learningMapsDataObj.filter(el => allowedTechnologies.includes(el.technology))
Alternatively, you can also check if el.technology is like some of the values to filter by:
const allowedTechnologies = ["Networking", "Security"];
learningMapsDataObj.filter(el => { // Linebreak (thus function body) for readability
return allowedTechnologies.some(allowedTech => allowedTech === el.technology);
})
Map to string array
It seems your filter is in a form similar to this:
const filter = [
{ value: "Networking" }.
{ value: "Security" }
];
But it would be easier to use if it was in the form as shown in my examples above. You can achieve that by mapping the filter elements to their values:
const filter = [
{ value: "Networking" }.
{ value: "Security" }
];
const filterValues = filter.map(el => el.value);
// Now you can use filterValues like allowedTechnologies
let result = learningMapsDataObj.filter(el => el.technology === "Networking" || el.technology === "Security" );
You can filter the array with an array of criterias like this
const filterCriteria= ["Networking", "Security"]
const filteredArray = learningMapsDataObj.filter((item) => filterCriteria.includes(item.technology));
if you want to set a variable for fields you want to filter, you can also do something like this
const filterField = 'technology'
const filterCriteria= ["Networking", "Security"]
const filteredArray = learningMapsDataObj.filter((item) => filterCriteria.includes(item[filterField]));

Vue JS real-time search on object's key/value pairs

I'm using Vue JS 2 as part of my Nuxt JS project to build a real-time search filter whereby a user can start typing and have results matched based on what they've typed.
I've got a search input, binded to the model of applicantModalSearch as a String, and an object held in applicantModalData with key/value pairs.
The data inside of applicantModalData is unknown, and not initially populated on page load, it's updated as part of the user selection from a v-for element, i.e: the user clicks a button to preview data in a modal, and the index item for that v-for item is added to applicantModalData
I'd like to be able to dynamically search for both keys, and the values, and not just the value and have results returned, right now my error is:
this.applicantModalData.filter is not a function
export default {
data () {
return {
applicantModalIsShown: false,
applicantModalData: null,
applicantModalSearch: ''
}
},
methods: {
/*
** Applicant modal data
*/
toggleApplicantModal (applicant) {
this.applicantModalData = applicant ?? null
this.applicantModalIsShown = !this.applicantModalIsShown
}
},
computed: {
/*
** Filter applicant data for ease
*/
filteredApplicantData () {
if (this.applicantModalSearch) {
return this.applicantModalData.filter((item) => {
return this.applicantModalSearch.toLowerCase().split(' ').every(v => item.toLowerCase().includes(v))
})
} else {
return this.applicantModalData
}
}
}
}
An example object that I need to filter and return could be:
{
"_method": "POST",
"honey_key": "test",
"honey_version": null,
"client_user_agent_string": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
"application_source_url": "https://example.com/",
"IsIframe": "1",
"AppFirstName": "John",
"AppLastName": "Doe"
}
So if I type in something like "John" into the search element (applicantModalSearch) I expect to get from my computed property:
{
"AppFirstName": "John",
}
or if I type into the search element "lastname" I expect to see:
{
"AppLastName": "Doe"
}
What am I missing in my code?
UPDATE
So after implementing:
filteredApplicantData() {
if (this.applicantModalSearch) {
return Object.fromEntries(
Object.entries(this.applicantModalData).filter(([key, value]) => {
if (value == null) value = 'null'
return this.applicantModalSearch
.toLocaleLowerCase()
.split(" ")
.every(
(word) =>
key.toLocaleLowerCase().includes(word) ||
value.toLocaleLowerCase().includes(word)
);
})
);
}
return this.applicantModalData;
},
and adding more data, strangely the code will fail and only ever get to client_user_agent_string in the list (I've added more data)
is there a weird character maybe in the object?
FINAL UPDATE
Turns out that the following is needed to resolve and complete the function as part of the Object.entries part:
if (value == null) value = 'null'
I followed your logic for comparing the values, but applied them to both the key and value. I tested it on the example that you gave.
computed: {
filteredApplicantData() {
if (this.applicantModalSearch) {
return Object.fromEntries(
Object.entries(this.applicantModalData).filter(([key, value]) => {
return this.applicantModalSearch
.toLocaleLowerCase()
.split(" ")
.every(
(word) =>
key.toLocaleLowerCase().includes(word) ||
value.toLocaleLowerCase().includes(word)
);
})
);
}
return this.applicantModalData;
},
},
Some explanation: Object.entries returns an array of the given object's key-value pairs as an array [key, value]. This allows us to iterate/filter on that array and reconstruct the object with the opposite method Object.fromEntries.

Vue js searching array

I am trying to search an array for query string on a json object. The returned fields are an array of filtered results. Currently, the list is returning the name field but not the number field.
computed: {
search: function () {
let self = this
let filtered = []
filtered = self.jcontacts.filter(function(contact){
return contact.firstname.toLowerCase().indexOf(self.query.toLowerCase())>=0 ||
contact.lastname.toLowerCase().indexOf(self.query.toLowerCase())>=0;
contact.email.toLowerCase().indexOf(self.query.toLowerCase()) >=0 ||
contact.phonenumber.toLowerCase().indexOf(self.query.toLowerCase()) >=0;
}
);
return this.contacts = filtered
}
},
}
The filtered method in the search method is not showing the number. An example of the json is below:
[
{
"id": 1,
"phoneNumber": [
"3908902"
],
"email": [
"jamie#fox.com"
],
"firstname": "Jamie",
"lastname": "Fox"
}]
Beware of case phoneNumber != phonenumber
phoneNumber is stored as array not string, so you cant look for it like that (use .includes() function for example)
For code formatting consider to store self.query.toLowerCase() as variable or another computed property
It is a typo. Check out field phoneNumber is filtered as phonenumber.
It is an array so you can do it as,
contact.phoneNumber.forEach( number => {
if(number.toLowerCase().indexOf(self.query.toLowerCase()) >=0)
return true;
});
I think similarly you can write it down for email as well because it is also an array.

Find nested object that contains a value

I have an object containing multiple other objects, inside these nested objects is an array containing multiple objects, each with a uid. I'm trying to loop over the objects and find the object that contains a particular uid.
My data looks like this
const data = {
"3c5671fde44f44f9ad59d59eb810d87e": {
"heading": "Heading 1",
"items": [
{
"content": {
"uid": "4fcd5f4af7a448d48463d4e0a11297d9"
}
},
{
"content": {
"uid": "31f975440a0a431592e127b2891cd142"
}
}
]
},
"ea80e8315b554c9bb40958a6cacf4b0c": {
"heading": "Heading 2",
"items": [
{
"content": {
"uid": "d6de8db4c2a74da6915a44d3964277d6"
}
}
]
}
}
The uid I want to search for is d6de8db4c2a74da6915a44d3964277d6 when found I want to return it's parent object so I can access the heading property.
Current code looks like this but it doesn't work, it's been a long day so I'm likely missing something really simple.
const currentUid = "d6de8db4c2a74da6915a44d3964277d6";
const currentHeading = Object.keys(data).forEach(section => {
return data[section].items.filter(item => {
return item.content.uid === currentUid;
});
});
When debugging it successfully evaluates to true when it finds the correct uid, it just doesn't return anything.
Any help welcome!
forEach is meant just for looping the array, use find to find the key of your object from Object.keys(data). And inside the callback use some instead of filter to check the existance. This solution will result in either the key of the object or null. To get the object, just check that a key is returned and then use that key to get the object:
const currentHeadingKey = Object.keys(data).find(section => {
return data[section].items.some(item => {
return item.content.uid === currentUid;
});
});
const currentHeading = currentHeadingKey != null ? data[currentHeadingKey] : null;
currentHeading is now either the whole object if found, null otherwise. You can access the heading property of that object.
Note: Since the callbacks of both find and some have only one statement in them, you can use the implicit return of arrow function to shorten the code:
const currentHeadingKey = Object.keys(data).find(section =>
data[section].items.some(item => item.content.uid === currentUid)
);
Consider using the Object.values() method instead, to extract the "parent heading" for the supplied uid.
Taking this approach, you can iterate the values of data, and filter those section values that contain items matching the uid. In the answer below, this is done via:
return items.some(item => item.content.uid === currentUid)
Finally, you can map() the filtered sections to acquire the corresponding heading(s) of section with matching uid items:
function findHeading(data, uid) {
return Object.values(data).filter(section => {
// Find any item with matching uid in this section, filter this section accordingly
return section.items.some(item => item.content.uid === currentUid)
})
.map(section => {
// Map heading from any sections matching item uid
return section.heading
});
}
const data = {
"3c5671fde44f44f9ad59d59eb810d87e": {"heading": "Heading 1","items": [{"content": {"uid": "4fcd5f4af7a448d48463d4e0a11297d9"}},{"content": {"uid": "31f975440a0a431592e127b2891cd142"}}]},"ea80e8315b554c9bb40958a6cacf4b0c": {"heading": "Heading 2","items": [{"content": {"uid": "d6de8db4c2a74da6915a44d3964277d6"}}]}
}
const currentUid = "d6de8db4c2a74da6915a44d3964277d6";
console.log(findHeading(data, currentUid))

React, Firebase: Access values of JSON and also get key value

I am beginner working with firebase, react. I am able to get the required data from firebase based on userEmail. But I am very confused in accessing the data.
firebase.database().ref('/users').orderByChild('email').equalTo(userEmail).on('value', data => {
console.log('data: ', data);
})
I get the following output:
data: Object {
"-Lhdfgkjd6fn3AA-": Object {
"email": "t5#gmail.com",
"favQuote": "this is it",
"firstName": "t5",
"lastName": "l5",
},
}
Please help me how to access all values ("-Lhdfgkjd6fn3AA-" , firstname, lastname, email and favQuote) into variables like: data.firstName, data.lastName, data.key, etc . Thank you.
let data = {
"-Lhdfgkjd6fn3AA-": {
"email": "t5#gmail.com",
"favQuote": "this is it",
"firstName": "t5",
"lastName": "l5",
},
};
console.log(Object.keys(data))//returning an array of keys, in this case ["-Lhdfgkjd6fn3AA-"]
console.log(Object.keys(data)[0])
console.log(Object.values(data))//returning an array of values of property
console.log(Object.values(data)[0].email)
Do need to be careful that the above code with the hardcoded "0" as index because it assumed that your data object has only one key. If you have more key, you can't simply replace index either because property of object has no predictable sequence
It's really a JavaScript question. I had to figure this out too. ...this works.
var p;
var thisLine;
p = docu.data();
for (var k in p) {
if (p.hasOwnProperty(k)) {
if (isObject(p[k])) {
thisLine = p[k];
Object.keys(thisLine).forEach(function (key, index) {
console.log(key, index);
});
}
}
}
function isObject(obj) {
return obj === Object(obj);
}
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
So your first step is that you need to loop over the snapshot in your on() callback.
The second step is that you need to call Snapshot.val() to get the JSON data from the snapshot. From there you can get the individual properties.
firebase.database().ref('/users').orderByChild('email').equalTo(userEmail).on('value', snapshot => {
snapshot.forEach(userSnapshot => {
let data = userSnapshot.val();
console.log('data: ', data);
console.log(data.email, data.firstname);
});
})

Categories

Resources