Filter nested collections using Rxjs - javascript

I need to filter the following json data structure using Rxjs operators,
consider the following example, we have list of hotels and each hotel as rooms underneath it, we need to get all hotels that have available rooms in it,
so consider the following input as:
var hotels = [
{
"hotel": "hotel 1",
"rooms": [
{
"name": "room 1",
"flexible": true
},
{
"name": "room 2",
"flexible": false
}
]
},
{
"hotel": "hotel 2",
"rooms": [
{
"name": "room 1",
"flexible": false
},
{
"name": "room 2",
"flexible": false
}
]
}
];
and the desired output should be as the following:
var availableRooms = [
{
"hotel": "hotel 1",
"rooms": [
{
"name": "room 1",
"flexible": true
}
]
}
];
so How can I apply this in Rxjs?
Thanks

Are you sure that we need a RxJx?
function isAvailable(room) { return room.flexible };
hotels
.map(hotel => ({ ...hotel, rooms: hotel.rooms.filter(isAvailable) }))
.filter(hotel => hotel.rooms.length);
If hotel goes one by one - then use the same approach but with RxJs operators.
Edited
Solution with RxJS
function isAvailable(room) { return room.flexible };
from(hotels).pipe(
map(hotel => ({ ...hotel, rooms: hotel.rooms.filter(isAvailable) })),
filter(hotel => hotel.rooms.length),
toArray(),
).subscribe(console.log)

Related

How to convert array of object into nested array of object in Javascript?

I am having an array of objects like this
[
{
name: "dhanush",
goals: ["goal 1","goal 2"],
goalAmount: ["10000","20000"]
},
{
name: "kumar",
goals: ["goal 3", "goal 4"],
goalAmount: ["30000","40000"]
},
{
name: "test",
goals: ["goal 5"],
goalAmount: ["50000"],
}
]
Is that possible to convert the above array of object into the below structure like this
[
{
"name": "dhanush",
"goals": "---",
"goalAmount":"---",
"subRows": [
{
"name": "",
"goals": "goal 1",
"goalAmount":" 10000"
},
{
"name": "",
"goals": "goal 2",
"goalAmount":" 20000"
}
]
},
{
"name": "kumar",
"goals": "---",
"goalAmount":"---",
"subRows": [
{
"name": "",
"goals": "goal 3",
"goalAmount":" 30000"
},
{
"name": "",
"goals": "goal 4",
"goalAmount":" 40000"
}
]
},
{
"name": "Test",
"goals": "goal 5",
"goalAmount":"50000"
}
]
In the first data, you can see the user has multiple goals (which means the array length is more than 1), If the goal length is more than one, need to create another key and move the goal data into the above structure like this.
Why I am doing this because I got an ask to create a table that needs to support expandable rows. I used #tanstack/react-table for this row expansion. Here you can find the working demo link - https://codesandbox.io/s/tanstack-table-expansion-1t77ks?file=/src/App.js
In the demo, you can see the row can expand. For the expansion that table requires the data format like this.
I tried to do something like this,
var data = [
{
name: "dhanush",
goals: ["goal 1","goal 2"]
},
{
name: "kumar",
goals: ["goal 3", "goal 4"]
},
{
name: "test",
goals: ["goal 5"]
}
]
let result = data.map((val,i) => {
return {
name: val.name,
...(val.goals.length === 1 && {goals: val.goals[0]}),
[val.goals.length > 1 && 'subRows']: data.map((t,j) => {
return{
name: "",
goals: val.goals[j]
}
}),
}
})
But the output I am getting like this instead of the actual structure
[
{
"name": "dhanush",
"subRows": [
{
"name": "",
"goals": "goal 1"
},
{
"name": "",
"goals": "goal 2"
},
{
"name": ""
}
]
},
{
"name": "kumar",
"subRows": [
{
"name": "",
"goals": "goal 3"
},
{
"name": "",
"goals": "goal 4"
},
{
"name": ""
}
]
},
{
"name": "test",
"goals": "goal 5",
"false": [
{
"name": "",
"goals": "goal 5"
},
{
"name": ""
},
{
"name": ""
}
]
}
]
Could you please help to achieve this?
This here
[val.goals.length > 1 && 'subRows']: data.map((tm j) => {
evaluates to false if there's 1 goal or less, and results in the string property false. Then you're mapping the whole data again inside that for some reason. Only map over the goals of the current element you're iterating over, the val.goals.
Because the different possible resulting objects are pretty differently strucured, I think this would be easier to manage if they were entirely separate - return { name, goals: goals[0] } if there's only one goal, and return a completely different object mapping over the goals otherwise.
var data=[{name:"dhanush",goals:["goal 1","goal 2"],goalAmount:["10000","20000"]},{name:"kumar",goals:["goal 3","goal 4"],goalAmount:["30000","40000"]},{name:"test",goals:["goal 5"],goalAmount:["50000"]}];
const result = data.map(({ name, goals, goalAmount }) => {
return goals.length === 1
? { name, goals: goals[0], goalAmount: goalAmount[0] }
: {
name,
goals: '---',
subRows: goals.map(
(goal, i) => ({ name: '', goal, goalAmount: goalAmount[i] })
)
};
});
console.log(result);
Don't try to do this all in a single object literal, it's a confusing way to create properties conditionally. Just write normal conditional statements.
To get the goalAmount, use the index argument to the map() callback function so you can get the corresponding element in another array.
var data = [
{
name: "dhanush",
goals: ["goal 1","goal 2"],
goalAmount: ["10000","20000"]
},
{
name: "kumar",
goals: ["goal 3", "goal 4"],
goalAmount: ["30000","40000"]
},
{
name: "test",
goals: ["goal 5"],
goalAmount: ["50000"],
}
]
var result = data.map(({
name,
goals,
goalAmount
}) => {
let item = {
name
};
if (goals.length == 1) {
item.goals = goals[0];
item.goalAmount = goalAmount[0];
} else {
item.goals = "---";
item.subRows = goals.map((g, i) => ({
name: "",
goals: g,
goalAmount: goalAmount[i]
}));
}
return item;
});
console.log(result);

React Native - sort array based off values in common

Hey guys I have the following array that's used to display a flatlist within my app.
Array [
Object {
"data": "Item 1",
"id": "1",
"subjects": "1,8,9,23,11,15,16,14,20",
},
Object {
"data": "Item 2",
"id": "2",
"subjects": "8,11,2,4,16,19",
},
Object {
"data": "Item 3",
"id": "3",
"subjects": "16,20,14,11,9,2",
},
Object {
"data": "Item 4",
"id": "4",
"subjects": "1,16,19",
},
]
However I would like to sort this array based off the subjects value. In the app the user can select a couple of subjects which are represented by numbers so lets say the users selected subjects are:
11, 4, 2, 1
I would like to sort the array so that the items with 3 or more subjects in common with the user are sorted to the top and then items with two and then 1 and then none so the array above should look like this after sorting:
Array [
Object {
"data": "Item 2",
"id": "2",
"subjects": "8,11,2,4,16,19",
},
Object {
"data": "Item 1",
"id": "1",
"subjects": "1,8,9,23,11,15,16,14,20",
},
Object {
"data": "Item 3",
"id": "3",
"subjects": "16,20,14,11,9,2",
},
Object {
"data": "Item 4",
"id": "4",
"subjects": "0,16,19",
},
]
How can I achieve this?
I have been searching around the array sort function:
Array.prototype.sort()
However I have only seen how to sort based off number comparisons I have never seen an array sorted based off values in common. Please could someone help me with this!
EDIT
Array [
Object {
"data": "Item 2",
"id": "2",
"subjects": "8,11,2,4,16,19",
"ranking": "green",
},
Object {
"data": "Item 1",
"id": "1",
"subjects": "1,8,9,23,11,15,16,14,20",
"ranking": "amber",
},
Object {
"data": "Item 3",
"id": "3",
"subjects": "16,20,14,11,9,2",
"ranking": "amber",
},
Object {
"data": "Item 4",
"id": "4",
"subjects": "0,16,19",
"ranking": "red",
},
]
You could create an object with counts of selected subjects and sort descending by this value.
const
data = [{ data: "Item 1", id: "1", subjects: "1,8,9,23,11,15,16,14,20" }, { data: "Item 2", id: "2", subjects: "8,11,2,4,16,19" }, { data: "Item 3", id: "3", subjects: "16,20,14,11,9,2" }, { data: "Item 4", id: "4", subjects: "1,16,19" }],
selected = [11, 4, 2, 1],
counts = data.reduce((r, { id, subjects }) => {
r[id] = subjects
.split(',')
.reduce((s, v) => s + selected.includes(+v), 0);
return r;
}, {});
data.sort((a, b) => counts[b.id] - counts[a.id]);
console.log(data);
console.log(counts);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filter inside a nested array with objects

I have an array that contains three book objects. Each book object has an array of bookIds . I want to filter by bookId (3,1) in a fast and efficient way since my array will grow up easly in the future. I tried with map, but it change my original array even with a deepCopy ! Is there a way to use the filter function instead without using recursivity ?
this.booksList =
[
{
"books": [
{
"bookId": 3
},
{
"bookId": 2
}
],
"id": 1,
"name": "Name 1",
"description": "desc 1"
},
{
"books": [
{
"bookId": 5
},
{
"bookId": 2
}
],
"id": 2,
"name": "Name 2",
"description": "desc 2"
},
{
"books": [
{
"bookId": 1
},
{
"bookId": 3
}
],
"id": 3,
"name": "Name 3",
"description": "desc 3"
}
]
The map approach :
let results = this.books.map(function (book) {
book.books = book.books.filter(x => x.bookId == 1 || x.bookId == 3);
return book;
}).filter(({ books }) => books.length);
Result with map : not expected result !
[
{
"books": [
{
"bookId": 3
}
],
"id": 1,
"name": "Name 1",
"description": "desc 1"
},
{
"books": [
{
"bookId": 1
},
{
"bookId": 3
}
],
"id": 3,
"name": "Name 3",
"description": "desc 3"
}
]
expected results :
[
{
"books": [
{
"bookId": 3
},
{
"bookId": 2
}
],
"id": 1,
"name": "Name 1",
"description": "desc 1"
},
{
"books": [
{
"bookId": 1
},
{
"bookId": 3
}
],
"id": 3,
"name": "Name 3",
"description": "desc 3"
}
]
Thanks,
I think you are looking for filter and some -
const input =
[{books:[{bookId:3},{bookId:2}],id:1,name:"Name 1",description:"desc 1"},{books:[{bookId:5},{bookId:2}],id:2,name:"Name 2",description:"desc 2"},{books:[{bookId:1},{bookId:3}],id:3,name:"Name 3",description:"desc 3"}]
const query =
[3, 1]
const result =
input.filter(({ books = [] }) =>
books.some(({ bookId = null }) => query.includes(bookId))
)
console.log(JSON.stringify(result, null, 2))
Output -
[
{
"books": [
{
"bookId": 3
},
{
"bookId": 2
}
],
"id": 1,
"name": "Name 1",
"description": "desc 1"
},
{
"books": [
{
"bookId": 1
},
{
"bookId": 3
}
],
"id": 3,
"name": "Name 3",
"description": "desc 3"
}
]
const booksList=[
{books:[{bookId:3},{bookId:2}],id:1,name:"Name 1",description:"desc 1"},
{books:[{bookId:5},{bookId:2}],id:2,name:"Name 2",description:"desc 2"},
{books:[{bookId:1},{bookId:3}],id:3,name:"Name 3",description:"desc 3"},
];
const byBookIDs = (...IDs) =>
booksList.filter(b => b.books.some(b => IDs.includes(b.bookId)));
console.log(byBookIDs(1, 3));
MDN Array Filter - to return a subset
MDN Array Some - to match (and return) as soon as some
MDN Array Includes - to find item in array

Exclude items from Array.map()

I am trying to exclude items that contain Packages in packageData.type. To show only the packageData that has items with packageData.type as Add-on.
Array is as example
{
"packageData": [
{
"title": "Title 1",
"type": [
"Packages"
]
},
{
"title": "Title 2",
"type": [
"Add-on"
]
},
{
"title": "Title 3",
"type": [
"Add-on"
]
},
{
"title": "Title 4",
"type": [
"Add-on"
]
}
]
}
and this is how I am currently trying to remove results which is giving zero results.
<ul>
{packageData.map(function() {
if (packageData.type ==! "Packages") {
return (
<li key={packageData.id}>
<a>
<img src={packageData.heroImage.sizes.src} alt=""/>
<h3>{packageData.title}</h3>
</a>
</li> );
} else {
return null;
};
})}
</ul>
How can I make this work? Or should I be filtering these results prior to displaying and then map the returned items?
You have to use filter before map, Filter will filter the array and then map will iterate through array. Below is the sample code
var data = {
"packageData": [
{
"title": "Title 1",
"type": [
"Packages"
]
},
{
"title": "Title 2",
"type": [
"Add-on"
]
},
{
"title": "Title 3",
"type": [
"Add-on"
]
},
{
"title": "Title 4",
"type": [
"Add-on"
]
}
]
};
data.packageData.filter((item) => { return item.type[0] !== 'Packages' }).map((item)=> { console.log(item); });

MongoDB, Mongoose, How to remove duplicate items from the doc.find results after populating that data?

I have an array of items in my database, some of these items have an array called relatedItems, which are basically an array of IDs, and these IDs could match some of the items inside the same document. I can run populate to transform the array of IDs into an array of objects, where each object is the populated item with the matched ID. But I also want to remove those items from the main array of items. Here is the example of my data structure:
{
"data": {
"getItems": [
{
"title": "item 09",
"_id": "5a56215426004a22c17ba733",
"relatedItems": ["5a5621a526004a22c17ba773", "5a56214b26004a22c17ba72a", "5a56215326004a22c17ba732", "5a56215726004a22c17ba735"]
},
{
"title": "item 10",
"_id": "5a56215726004a22c17ba735",
"thumbnail": {
"Key": ".../5a56215726004a22c17ba735/thumbnail.jpg"
}
},
{
"title": "item 11",
"_id": "5a56215326004a22c17ba732",
"thumbnail": {
"Key": ".../5a56215326004a22c17ba732/thumbnail.jpg"
}
},
{
"_id": "5a56216b26004a22c17ba747",
"relatedItems": []
},
{
"_id": "5a56216c26004a22c17ba748",
"relatedItems": []
}
]
}
}
And I am running the following code to populate the data:
let items = await ItemModel.find({ category }).limit(limit).sort({ itemOrder: 1 })
.populate({path: 'relatedItems', select: '_id thumbnail'}).sort({ itemOrder: 1 });
But as you know, after populating, and I am going to end up having duplicate items (one copy in the related items array and one in the main array) like so:
{
"data": {
"getItems": [
{
"title": "item 09",
"_id": "5a56215426004a22c17ba733",
"relatedItems": [
"_id": "5a5621a526004a22c17ba773",
"thumbnail": {
"Key": ".../5a5621a526004a22c17ba773/thumbnail.jpg"
}
},
{
"_id": "5a56214b26004a22c17ba72a",
"thumbnail": {
"Key": ".../5a56214b26004a22c17ba72a/thumbnail.jpg"
}
},
{
"title": "item 11",
"_id": "5a56215326004a22c17ba732",
"thumbnail": {
"Key": ".../5a56215326004a22c17ba732/thumbnail.jpg"
}
},
{
"title": "item 10",
"_id": "5a56215726004a22c17ba735",
"thumbnail": {
"Key": ".../5a56215726004a22c17ba735/thumbnail.jpg"
}
}
]
},
{
"title": "item 10",
"_id": "5a56215726004a22c17ba735",
"thumbnail": {
"Key": ".../5a56215726004a22c17ba735/thumbnail.jpg"
}
},
{
"title": "item 11",
"_id": "5a56215326004a22c17ba732",
"thumbnail": {
"Key": ".../5a56215326004a22c17ba732/thumbnail.jpg"
}
},
{
"_id": "5a56216b26004a22c17ba747",
"relatedItems": []
},
{
"_id": "5a56216c26004a22c17ba748",
"relatedItems": []
}
]
}
}
I would like to remove the copy from the main array, and currently, I have accomplished it via the following code:
let items = await ItemModel.find({ category }).limit(limit).sort({ itemOrder: 1 })
.populate({path: 'relatedItems', select: '_id thumbnail'}).sort({ itemOrder: 1 });
Promise.all(items.map(async item => {
if (item.relatedItems && item.relatedItems.length > 0) {
Promise.all(item.relatedItems.map(async relatedItem => {
related.push(relatedItem._id);
}));
}
}));
items = items.filter(item => !JSON.stringify(related).includes(item._id));
And it works, but I am wondering if there is a faster/safer or more MongoDB native way to do this?
Edit
One important downside to using my filter method is that .limit(limit) does not work properly anymore since the array will be trimmed down if it finds duplicates, the workaround is using .slice(0, limit) on the final array after filtering, but doesn't sound like it's the best way. Also I kinda feel I am going to run into some issues if I decide to add pagination to it too.

Categories

Resources