Mongodb IN and return elements not matched - javascript

I have a collection of players like this:
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d55"),
"id_test": 132
"name": "John Doe"
},
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d33"),
"id_test": 90
"name": "Tom White"
},
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d31"),
"id_test": 999
"name": "Mike Barry"
}
I have an array of Ids with id_test:
const arrayIds = [ 132, 43, 90, 555];
Then I want to get elements not matched in array (not in collection with $nin). Im my example I need to output: [43, 555]
Something like this: (but I want to know if it's possible with one single query):
const players = await db.collection('players').find(
{ id_test: { "$in": arrayIds } } )
.toArray();
const playersIds = players.map(e => e.id_test); // [132, 90]
const final = arrayIds.filter(i => !playersIds.includes(i)) // [43, 555]

Yes, you can do that in a single query by aggregation,
First, we search the players, then create an array of their id_test, then by $setDifference get the difference you want
const players = await db.collection('players').aggregate(
[ { $match :
{
id_test : { "$in": arrayIds }
}
},
{
$group:
{
_id: null,
id_test: { $push: "$id_test" }
}
},
{ $project: { final:{ $setDifference: [ arrayIds , "$id_test" ] }, _id: 0 } }
]
);
const final = players.final // [43, 555]

Related

Adjust a 22-line JS function that now returns wrong counts with +1 / +2 differences for no reasons

My function further below seems fine yet the count is overstated in some instances, eg: should count '1' but shows '2'.
Data source for context:
{
"currency": "USD",
"services": [
{
"category": [
{"token": "token1"},
{"token": "token2"}
],
"price": 149
},
{
"category": [
{"token": "token3"},
{"token": "token4"}
],
"price": 149
}
]
},
{
"currency": "EUR",
"services": [
{
"category": [
{"token": "token1"},
{"token": "token2"}
],
"price": 149
},
{
"category": [
{"token": "token3"},
{"token": "token4"}
],
"price": 149
}
Goal: COUNT the frequency of category tokens per price, sorted by currency in their own objects.
Desired output schema (for illustration purposes, unrelated to above schema example):
{
"result": [
{
"currency": "USD",
"token": "Wellness",
"count": 1,
"price": 100
},
{
"currency": "USD",
"token": "Adventure",
"count": 1,
"price": 300
}
]
}
It appears that sometimes, the count is not right, by +1 or +2 difference for no apparent reasons.
My function, which outputs wrong counts:
const data = inputs.data;
const result = [];
let error = null;
try {
data.forEach(item => {
item.services.forEach(service => {
service.category.forEach(tokenObject => {
const token = tokenObject.token;
const existingToken = result.find(item => item.token === token && item.price === service.price && item.currency === item.currency);
if (existingToken) {
existingToken.count++;
} else {
result.push({currency: item.currency, token, count: 1, price: service.price});
}
});
});
});
} catch (e) {
error = "error";
}
return error ? [1, {error}] : [0, {result}]
Any way to make it "fool-proof" with some kind of "UNIQUE" safe guard?
Note: I'm beginner in JS.
I've used OpenAI's playground and it gave me the right function!
It suggested to "modify the existing code to use a Map data structure instead of an array to store the token count information. The Map will allow to keep track of each token, currency, and price combination as a key, and its count as the value"
const data = inputs.data;
const result = [];
let error = null;
try {
const tokenMap = new Map();
data.forEach(item => {
item.services.forEach(service => {
service.category.forEach(tokenObject => {
const token = tokenObject.token;
const key = `${token}-${service.price}-${item.currency}`;
if (tokenMap.has(key)) {
tokenMap.set(key, tokenMap.get(key) + 1);
} else {
tokenMap.set(key, 1);
}
});
});
});
tokenMap.forEach((count, key) => {
const [token, price, currency] = key.split('-');
result.push({currency, token, count, price: Number(price)});
});
} catch (e) {
error = "error";
}
return error ? [1, {error}] : [0, {result}]

Insert new JSON objects in nested JS array based on condition

For one of my e-commerce application requirement, I have a nested array of the form (Sample):
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
]
To this array, I need to insert new objects to the array 'labels' if the group value equals 'upper-wear'.
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
]
This is what I tried so far, considering that for now I only need to insert to single label (i.e. 'upper-wear') (in future, there can be multiple labels category 'upper-wear', 'bottom-wear', to be inserted into):
const updatedArray = data.map((datum) => {
if (datum.group === 'upper-wear') {
return {
...datum,
labels: [...datum.labels, ...newDataToInsert]
};
}
});
console.log(updatedArray);
But there seems to be a silly issue that I am missing as the result returns like this:
[
{
id: 1,
group: 'upper-wear',
labels: [ [Object], [Object] ],
popular: true
},
undefined
]
I know there may be better approaches available, but this is what I can think of as the minimum solution for now.
any help to resolve the current or any better solution will be highly appreciated.
Try with this
updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
})
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
];
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
];
const updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
});
console.log(updatedArray)
Explaination
Here while mapping the data, we check for the condition
IF
If it matches then we will first copy the whole object from the variable b return { ...b }
after that we take another variable with the same name lables return { ...d, labels: d.labels.concat(newDataToInsert) },As per the JSON default nature the new variable with the same name will hold the latest value
Here in labels we first take a copy of old data and then merge it with newDataToInsert array labels: d.labels.concat(newDataToInsert), It will merge 2 arrays and store them in JSON with the name labels
Else
In else we just return the current values else { return d; }
You don't actually need to iterate with map over the array. Just find an object in the array and change what you want.
const data=[{id:1,group:"upper-wear",labels:[{type:"shirts",quantity:"20"}],popular:true},{id:2,group:"bottom-wear",lables:[{type:"trousers",quantity:"31"}],popular:true}];
const newDataToInsert=[{type:"blazers",quantity:19}];
data.find(({ group }) => group === 'upper-wear')?.labels.push(...newDataToInsert);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're not returning all objects from your map. you're only returning a result when your criteria is met. This is resulting in your undefined objects...
const data = [
{ "id": 1, "group": "upper-wear", "labels": [ { "type": "shirts", "quantity": "20", }, ], popular: true },
{ "id": 2, "group": "bottom-wear", "lables": [ { "type": "trousers", "quantity": "31", }, ], popular: true },
]
const newDataToInsert = [ { "type": 'blazers',"quantity": 19 }, ]
const updatedArray = data.map(datum => {
if (datum.group === 'upper-wear') datum.labels = [...datum.labels, ...newDataToInsert]
return datum
});
console.log(updatedArray);
You can use Array#find to locate the desired group and then change labels for the group found. There are two options depending on how many items you would like to insert. Use Array#push to add the desired item; use forEach for more than one item:
const searchgroup = "upper-wear";
const target = data.find(({group}) => group === searchgroup);
target.labels.push(...newDataToInsert); //For one item to insert
//newDataToInsert.forEach(label => target.labels.push( label )); //For more than one item
const data = [{"id": 1, "group": "upper-wear", "labels": [{"type": "shirts", "quantity": "20"},],popular: true }, {"id": 2, "group": "bottom-wear", "lables": [{"type": "trousers", "quantity": "31", },],popular: true}];
const newDataToInsert = [{"type": 'blazers', "quantity": 19}];
//group to find
const searchgroup = "upper-wear";
//target element in data
const target = data.find(({group}) => group === searchgroup);
//check if group was found
if( target ) {
//if there's only one product in newDataToInsert us this:
//target.labels.push(...newDataToInsert);
//if you have more than one product to be inserted use this; also works for one
newDataToInsert.forEach(label => target.labels.push( label ));
} else {
console.log( `No such group found: ${searchgroup}!` );
}
console.log( data );

how to update a particular json data object which belongs to browser local-storage

This is Browser localstorage Object referred as dataset
let dataset = localStorage.getItem('dataset') !== null ? leech : [];
[
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
This is the initial data object available I want to add more field to a particular id.
This is what I want :
[
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd",
"status":1
}
]
This my code to find the particular id
const user = dataset.find(user => user.id == 456);
Now how can I add status to user and update the user in the dataset?
You've already found the user by using Array.prototype.find() so all you need to do then is add the status property
// const dataset = JSON.parse(localStorage.getItem("dataset"))
const dataset = [{"id":123,"name":"abc"},{"id":456,"name":"bcd"}]
const user = dataset.find(({ id }) => id === 456)
if (user) {
user.status = 1
}
console.info(dataset)
.as-console-wrapper { max-height: 100% !important }
If you then want to store the modified data back into localStorage, use localStorage.setItem() and JSON.stringify()
localStorage.setItem("dataset", JSON.stringify(dataset))
If you want keep dataset initial value, and would like to get a new array, you can use Array.reduce() method.
const dataset = [
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
const output = dataset.reduce((acc, cur) => {
if (cur.id === 456) cur.status = 1;
acc.push(cur);
return acc;
}, []);
console.log(output);
If you want to update dataset, you can use Array.forEach() method.
const dataset = [
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
dataset.forEach(user => {
if (user.id === 456) user.status = 1;
});
console.log(dataset);
You could do with Array#Findindex with callback return function. so could pass the originaldata,searchId and update object. In this method you could updated object easily
Why i suggest findIndex
Because findindex not running entire loop or iteration. If the match
detect on first iteration they will break the loop and returning the
result.For long iteration its more faster than other loop (reduce,forEach)
const data = [ { "id": 123, "name": "abc" }, { "id": 456, "name": "bcd" } ]
function update(dataset,searchId,addtionObject){
let ind = dataset.findIndex(({id}) => id == searchId);
dataset[ind] = {...dataset[ind],...addtionObject}; //join the new and old array
return dataset
}
console.log(update(data,456,{status:1}))
If you want to create new state objet, you can use immer for that.
Immer will produce the nextState based on the mutations to the draft state.
import produce from "immer";
const baseState = [
{
id: 123,
name: "abc",
},
{
id: 456,
name: "bcd",
},
];
const nextState = produce(baseState, (draftState) => {
draftState[1].status = 1;
});

merge duplicate objects in an array and combine sub array of each object

I am trying to merge objects based off of Id, and merge each array that lives inside each account (object), but instead of merging the contents of accountList, the code overwrites the array, if there is a matching id.
I've made a new array and used the .find method to find matching objects based off there id, but stuck on how to merge the accountList together
const accounts = [
{
"Id": 103,
"accountList": [
{}
]
},
{
"Id": 103,
"accountList": [
{
"tokenId": "5aasdasdsdnjn3434nadd",
"featureId": 2840
}
]
},
{
"Id": 112,
"accountList": [
{
"tokenId": "5d30775bef4a722c38aefaaa",
"featureId": 2877
}
]
},
{
"Id": 112,
"accountList": [
{
"tokenId": "5d30775bef4a722c38aefccc",
"featureId": 2856
}
]
}
]
let result = [];
accounts.forEach(account => {
let match = result.find(r => r.Id === account.Id);
// console.log(match)
if(match) {
Object.assign(match, account);
//tried using spread operator instead of object assign, but didnt work
// match = {...match, ...account}
} else {
result.push(account);
}
});
console.log( JSON.stringify(result, null, 2))
The result which i need is to merge the object based off their id, and merge the contents of the accountList together, like so:
[
{
"Id": 103,
"accountList": [
{
"tokenId": "5aasdasdsdnjn3434nadd",
"featureId": 2840
}
]
},
{
"Id": 112,
"accountList": [
{
"tokenId": "5d30775bef4a722c38aefaaa",
"featureId": 2877
},
{
"tokenId": "5d30775bef4a722c38aefccc",
"featureId": 2856
}
]
}
]
I think, reduce() would do the job:
const accounts = [{"Id":103,"accountList":[{}]},{"Id":103,"accountList":[{"tokenId":"5aasdasdsdnjn3434nadd","featureId":2840}]},{"Id":112,"accountList":[{"tokenId":"5d30775bef4a722c38aefaaa","featureId":2877}]},{"Id":112,"accountList":[{"tokenId":"5d30775bef4a722c38aefccc","featureId":2856}]}];
const result = [...accounts
.reduce((r, o) => {
const record = r.get(o.Id)||{}
r.set(o.Id, {
Id: o.Id,
accountList: [
...(record.accountList||[]),
...o.accountList.filter(o =>
Object.keys(o).length != 0)
]
})
return r
}, new Map())
.values()]
console.log(result);
.as-console-wrapper {min-height: 100%}
You can try to use Array.concat:
let result = [];
accounts.forEach(account => {
let match = result.find(r => r.Id === account.Id);
// console.log(match)
if(match) {
match.accountList = match.accountList.concat(account.accountList);
} else {
result.push(account);
}
});
for (let res of result) {
console.log('res.Id: ', res.Id, res.accountList)
}
// res.Id: 103 [ {}, { tokenId: '5aasdasdsdnjn3434nadd', featureId: 2840 } ]
// res.Id: 112 [ { tokenId: '5d30775bef4a722c38aefaaa', featureId: 2877 },
// { tokenId: '5d30775bef4a722c38aefccc', featureId: 2856 } ]
Using Array.prototype.reduce we can accumulate the results in the final result array.
In the reduce call back just find the matching object using Id and merge the accountList array and not the object as you were doing in your code.
const accounts=[{"Id":103,"accountList":[{}]},{"Id":103,"accountList":[{"tokenId":"5aasdasdsdnjn3434nadd","featureId":2840}]},{"Id":112,"accountList":[{"tokenId":"5d30775bef4a722c38aefaaa","featureId":2877}]},{"Id":112,"accountList":[{"tokenId":"5d30775bef4a722c38aefccc","featureId":2856}]}];
const result = accounts.reduce((acc, account) => {
let match = acc.find(r => r.Id === account.Id);
if(match) {
match.accountList.push(...account.accountList); //push previous array
} else {
const act = { ...account };
act.accountList = account.accountList.filter((obj) => Object.keys(obj).length);
acc.push(act);
}
return acc;
}, []);
console.log(result);
I think you could use match.accountList.push(...account.accountList); instead of the object assign, spread operator can be used to push the element into the result item(match):
let accounts = [{ "Id": 103, "accountList": [{}] }, { "Id": 103, "accountList": [{ "tokenId": "5aasdasdsdnjn3434nadd", "featureId": 2840 }] }, { "Id": 112, "accountList": [{ "tokenId": "5d30775bef4a722c38aefaaa", "featureId": 2877 }] }, { "Id": 112, "accountList": [{ "tokenId": "5d30775bef4a722c38aefccc", "featureId": 2856 }] }];
let result = [];
accounts.forEach(account => {
(match = result.find(r => r.Id === account.Id), match ? match.accountList.push(...account.accountList) : result.push(account))
});
console.log(result);
const isNotEmptyObject = objc => Object.entries(objc).length > 0;
function mergeAccounts(accounts) {
const uniqueAccounts = new Map();
accounts.forEach(account => {
if(uniqueAccounts.has(account.Id)) {
let uniqueAccount = uniqueAccounts.get(account.Id);
if(account.accountList && account.accountList.length > 0)
uniqueAccount.accountList.push(...account.accountList);
uniqueAccount.accountList = uniqueAccount.accountList.filter(isNotEmptyObject);
} else {
uniqueAccounts.set(account.Id, account);
}
});
return Array.from(uniqueAccounts.values());
}
This will merge all the accounts having same ids. Hope this helps :)

Adding values to an object whilst iterating over data

I have a set of JSON - that i need to iterate over as I essentially need to create a string which contains certain identifiers in it. At the moment I am mapping over the data and then over stimulus list. ( I have the index which from the event as I only need data from that index).
The problem I have is the cell_id is always the same . And I need to create a string.
const key = `cell_Id${cell_id}:step_Id${item.step_id}:study_id${studyId}:stim_id${item.stimulus_id}`
I suspect I need to add things to an object as I am interating over the data - what is the best way to do this?
thanks
Here is the JSON
{
"study_id": 16,
"data": [
{
"cell_id": 23,
"stimulus_list": [
{
"stimulus_id": 96,
"step_id": 62
}
]
},
{
"cell_id": 24,
"stimulus_list": [
{
"stimulus_id": 95,
"step_id": 61
}
]
}
]
}
data
.map(item => item.stimulus_list)
.map(item => item[index]);
You can have a nested map over the data and then flatten the result using reduce.
const obj = {
"study_id": 16,
"data": [
{
"cell_id": 23,
"stimulus_list": [
{
"stimulus_id": 96,
"step_id": 62
}
]
},
{
"cell_id": 24,
"stimulus_list": [
{
"stimulus_id": 95,
"step_id": 61
}
]
}
]
}
const res = obj.data
.map(item => {
return [].concat(item.stimulus_list.map(stimulus => `cell_Id${item.cell_id}:step_Id${stimulus.step_id}:study_id${obj.study_id}:stim_id${stimulus.stimulus_id}`))
}).reduce((acc, item) => {
acc = acc.concat(item)
return acc;
}, [])
console.log(res)

Categories