JS: Object value depending on regEx match - javascript

There are some objects in an array like this:
const result = [
{
"_id": "Dn59y87PGhkJXpaiZ",
"title": "Something",
"synonyms": [ "Anything", "else" ]
},
{ ... }
]
I do get this result by performing this:
Content.find({
$or: [
{ title: { $regex: new RegExp(term, 'i') } },
{ synonyms: { $regex: new RegExp(term, 'i') } }
]
}).toArray()
As you can see, I'm searching for title (string) or synonym (array) elements by a given search term.
So searching for some or any will give me the first document as my result.
In my component I do the output of the data like this:
render () {
return result.map((link, index) => {
return <Dropdown.Item
text={link.title}
key={index}
/>
})
}
But right now I do get the output Something for the dropdown item if I'm searching for any (term). For the user this doesn't make sense.
Of course any should give me the output Anything and some should give me the output Something.
In this example you can also search for thing and I would expect two output elements (of one single result document): Anything and Something.
I'm not quite sure how to modify the code to get this result. I think the best place to modify should be the react component (output) - not the server request result.

You could issue two separate queries on the server side to maintain clearly which part of your document was matched. This would ensure that for the doc which matches using synonyms was not used to return title.
const matchingTitles = Content.find(
{ title: { $regex: new RegExp(term, 'i') } }
}, {title: 1} ).toArray().map(x => x.title);
const matchingSynonyms = Content.find(
{ synonyms: { $regex: new RegExp(term, 'i') } }
}, {synonyms: 1} ).toArray().map(x => x.synonyms).reduce((x, y) => x.concat(y), []);
return Array.from(new Set([...matchingTitles, ...matchingSynonyms]));
I fetch the strings separately using two queries and then take the set union of them.
And on client side you could use these strings directly to display the search result.

Related

How can I filter through an array of objects based on a key in a nested array of objects?

I am having a hard time filtering through an array of objects based on a value in a nested array of objects. I have a chat application where a component renders a list of chats that a user has. I want to be able to filter through the chats by name when a user types into an input element.
Here is an example of the array or initial state :
const chats= [
{
id: "1",
isGroupChat: true,
users: [
{
id: "123",
name: "Billy Bob",
verified: false
},
{
id: "456",
name: "Superman",
verified: true
}
]
},
{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}
];
I want to be able to search by the Users names, and if the name exists in one of the objects (chats) have the whole object returned.
Here is what I have tried with no results
const handleSearch = (e) => {
const filtered = chats.map((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints an empty array on every key press
};
const handleSearch = (e) => {
const filtered = chats.filter((chat) =>
chat.users.filter((user) => user.name.includes(e.target.value))
);
console.log(filtered);
// prints both objects (chats) on every keypress
};
Expected Results
If the input value is "bat" I would expect the chat with Id of 2 to be returned
[{
id: "2",
isGroupChat: true,
users: [
{
id: "193",
name: "Johhny Dang",
verified: false
},
{
id: "496",
name: "Batman",
verified: true
}
]
}]
The second approach seems a little closer to what you're trying to accomplish. There's two problems you may still need to tackle:
Is the search within the name case insensitive? If not, you're not handling that.
The function being used by a filter call needs to return a boolean value. Your outer filter is returning all results due to the inner filter returning the array itself and not a boolean expression. Javascript is converting it to a "truthy" result.
The following code should correct both of those issues:
const filtered = chats.filter((chat) => {
const searchValue = e.target.value.toLowerCase();
return chat.users.filter((user) => user.name.toLowerCase().includes(searchValue)).length > 0;
});
The toLowerCase() calls can be removed if you want case sensitivity. The .length > 0 verifies that the inner filter found at least one user with the substring and therefore returns the entire chat objects in the outer filter call.
If you want to get object id 2 when entering bat you should transform to lowercase
const handleSearch = (e) =>
chats.filter(chat =>
chat.users.filter(user => user.name.toLowerCase().includes(e.target.value)).length
);
try this it should work
const handleSearch2 = (e) => {
const filtered = chats.filter((chat) =>
chat.users.some((user) => user.name.includes(e))
);
console.log(filtered);
};
filter needs a predicate as argument, or, in other words, a function that returns a boolean; here some returns a boolean.
Using map as first iteration is wrong because map creates an array with the same number of elements of the array that's been applied to.
Going the easy route, you can do this.
It will loop first over all the chats and then in every chat it will check to see if the one of the users' username contains the username passed to the function. If so, the chat will be added to the filtered list.
Note, I am using toLowerCase() in order to make the search non case sensitive, you can remove it to make it case sensitive.
const handleSearch = (username) => {
var filtered = [];
chats.forEach((chat) => {
chat.users.forEach((user) => {
if (user.name.toLowerCase().includes(username.toLowerCase())) {
filtered.push(chat);
}
});
});
console.log(filtered);
return filtered;
}
handleSearch('bat');

How to check if property of an objects of array matches with one of the values in another array of object

I know the header is way too complicated but that's how it is right now. Let me make it more clear for you.
There is an object like
const users= [
{
id:"1",
name:"John",
mail:"aaa#gmail.com",
},
{
id:"2",
name:"Joe",
mail:"bbb#gmail.com",
},
]
Then there is
const attendingUsers = [
{
id:"1",
name:"John",
mail:"aaa#gmail.com",
.....
},
{
id:"2",
name:"Joe",
mail:"bbb#gmail.com",
.....
},}
] 
Both of the arrays has different properties and I just want to get the ones that are important to me. What I need is to look through users array and find the ones that has the same ids as the ones from attendingUsers.
I have came up with users.filter(user => user.id == attendingUsers.map(attendingUser => attendingUser.id)); but that is simply returning empty array. Any idea what would be the best way to tackle this sort of a problem?
First off, you'll have to make sure if the ID types are correct. Users has Number type for IDs but attendingUsers has String type.
Let's say they're both the same type for the code below (I'm going with string).
You can turn the attendingUsers array into an array of strings with:
const attendingUsersIds = attendingUsers.map(attendingUser => attendingUser.id)
Then match the ids with:
const matchedUsers = users.filter(user => attendingUsersIds.includes(user.id))
If they're intended to not be the same type, you can use user.id.toString() to turn the Number into a String or parseInt(attendingUser.id) to turn the String into a Number.
We can use Array.map to create a new array of users, with a property isAttending added to each user.
We determine if they are attending by using Array.some to search for any matching attendee with the same id.
const users = [
{
id:1,
name:"John",
mail:"aaa#gmail.com",
},
{
id:2,
name:"Joe",
mail:"bbb#gmail.com",
},
{
id:3,
name:"Alice",
mail:"ccc#gmail.com",
}
]
const attendingUsers = [
{
id:"1",
name:"John",
mail:"aaa#gmail.com",
},
{
id:"2",
name:"Joe",
mail:"bbb#gmail.com",
}
]
const result = users.map(user => {
return { ...user, isAttending: attendingUsers.some(({id}) => id == user.id) };
});
console.log("Users (with attending property):", result);

Use regex to access object's fields

I am in a situation in which I may need to process a JSON that could contain varying key names, which I'd need to access via regex.
For simplicity, I could receive a list similar to:
x=[{ bus: "XYZ123", color:"red" },
{ minibus: "ZZZ999", color:"blue" } ]
and I need to have a function that could get XYZ123 and ZZZ999 within a map function.
I've tried x.map( e => e[/.*bus/]) but to no avail, returning [undefined,undefined]
Does anyone have any trick I could follow for this?
Thank you
var x = [{
bus: "XYZ123",
color: "red"
},
{
minibus: "ZZZ999",
color: "blue"
}
];
console.log(
x.map(it => it[Object.keys(it).find(key => key.endsWith('bus'))])
);
This version of the logic gets all the keys, and then finds the key that ends with bus. Assuming there is only one per object, it gets the first one, and then returns that keys value in each object.
You can use a for ... in loop to find a property name on an object by matching a regular expression.
const regex = /.*bus/;
const result = x.map((e) => {
for (var key in e) {
if (key.match(regex)) {
return e[key];
}
}
return undefined;
});
console.log(result);
Map return mapping... one to one. so if some of your data in array don't fulfills your condition then you return an undefined value on this index in array. eg.if you have array that have 100 items, and only 5 fulfills your condition, then a map will return 100 elements in 95 cases you will get undefined. You should use filter, if you need a new copy of your array you can just create it.
const x=[{ bus: "XYZ123", color:"red" },
{ minibus: "ZZZ999", color:"blue" } ,
{ motobicke: "ZZZ999", color:"black" } ]
const filterBy = (data, regex) => {
return Array.from(data).filter(item => Object.keys(item).some(key => regex.test(key)) )
}
console.log(filterBy(x, /bus/));

mach beginning javascript with Array

Good afternoon, I am trying to make a method that tells me the number of elements an Array has that starts with "RT:"
For this I have developed the following code:
public getRowsRTs(idProyecto){
this.twitterService.getTargets().subscribe((data) => {
this.Twitter = data;
});
let countRT = this.Twitter.filter( tweet => tweet.message.startsWith("RT:")).length;
return countRT;
}
}
Here, data returns all the Documents that Mongo extracts, and puts them in the Twitter Array that I have defined at the beginning of the component. Within this Array each element has different attributes, such as _id, message, date ... I want you to tell me how many of those documents, the message value, begins with RT: and to return it to me, this code , it does not give me any problem, but it does not give me absolutely nothing, I do not know if someone could help me.
If the array is filled with strings, this should work:
let regixForRTStart = /^RT:/;
startArray = ['RT:1', 'RT:2', 'Other', 'Stuff'],
count = startArray.filter(item => regixForRTStart.test(item))
// length
console.log(count.length);
use filter and startsWith methods.
const arr = [
{ message: "abc" },
{ message: "RT:" },
{ message: "RT: 2" },
{ message: "test" }
];
const count = arr.filter(({ message }) => message.startsWith("RT:")).length;
console.log(count);

Fastest way to filter object by multiple properties

I have an array of objects that I want to filter for a string. So I want to check multiple properties if they contain the filter string (case insensitive).
Here's the array:
[{
id: "01234",
name: "My Object 01234",
short_name: "MO01234"
}, ...]
So all of the following filter strings should match that object: 0123, obj, mO01 etc.
Here's what I have right now:
const filterString = this.filterString.toLowerCase();
return myObjects.filter(
entry => {
return
entry.id.toLowerCase().indexOf(filterString) >= 0 ||
entry.name.toLowerCase().indexOf(filterString) >= 0 ||
entry.short_name.toLowerCase().indexOf(filterString) >= 0;
}
);
Can you think of a faster/cleaner way to do that?
I don't think that you can do it faster, but cleaner may be something like that
const filterString = this.filterString.toLowerCase();
return myObjects.filter((entry) => {
return Object.values(entry).some((value) => {
return value.toLowerCase().includes(filterString)
})
});
If you are allowed to put additional properties in your object, perhaps you could concatenate id, name and short_name (already in lowercase) into a single string and store it in the object as e.g. search_key; then you'd only have to check that.
{
id: "01234",
name: "My Object 01234",
short_name: "MO01234",
search_key: "01234:my object 01234:mo01234"
}
return myObjects.filter(
entry => entry.search_key.indexOf(filterString) >= 0
);
One thing you have to be mindful of in this case is to prevent unintended matches that may arise because e.g. the last few characters of id and the first few characters of name together produce a match. This is why I used a : delimiter here, assuming that's a character that can't appear in an ID or a short name.
let objects = [{
id: "01234",
name: "My Object 01234",
short_name: "MO01234"
},
{
id: "test",
name: "test",
short_name: "test"
}];
const filter = (collection, searchFor) => {
return collection.filter(obj => Object.values(obj).reduce((a,b) => a || String(b).toLowerCase().indexOf(searchFor.toLowerCase()) > -1, false))
}
console.log(filter(objects, "0123"));
console.log(filter(objects, "obj"));
console.log(filter(objects, "mO01"));
You could also extend this function to take a set of columns as parameter to filter on.
Another version using Regex:
const filterRegex = (collection, searchFor) => {
return collection.filter(obj => Object.values(obj).reduce((a,b) => a || String(b).match(new RegExp(searchFor, 'gi')), false))
}
console.log(filterRegex(objects, "0123"));
console.log(filterRegex(objects, "obj"));
console.log(filterRegex(objects, "mO01"));

Categories

Resources