MongoDB $project embedded document to root level - javascript

Using the aggregate pipeline, I am trying to project an embedded document to the root level WITHOUT projecting each field individually.
For example, I want to project name from this collection to the root level:
[
{
_id: "1",
name: {
firstName: "John",
lastname: "Peters"
}
},
{
_id: "2",
name: {
firstName: "Mary",
lastname: "Jones"
}
}
]
This is what I am looking for:
[
{
firstName: "John",
lastname: "Peters"
},
{
firstName: "Mary",
lastname: "Jones"
}
]
Is there a way to do this without projecting each field individually? I don't want to have to do this:
db.collection.aggregate(
[
{
$project : {
"_id" : 0,
"firstName" : "$name.firstName",
"lastName" : "$name.lastName"
}
}
]

MongoDB 3.4 has the new stage in aggregation pipeline - $replaceRoot, which does exactly what was asked.
https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/

Here is the solution which uses JavaScript variable.
# Set Object for what to project
var projectWhat = {'_id' : 0};
# Fill Object with keys
Object.keys(db.coll.findOne().name).forEach(function(x){
projectWhat[x] = "$name." + x;
});
# Do Aggregate
db.coll.aggregate([{$project : projectWhat}])
And the output will be
{ "firstName" : "John", "lastname" : "Peters" }
{ "firstName" : "Mary", "lastname" : "Jones" }
Hope this helps.

You can use $replaceRoot like this:
db.collection.aggregate(
[
{
$replaceRoot : {
newRoot: {"$name"}
}
}
]
)
Also if you have a field in the root document you want to retain you can use a $mergeObjects to combine it with your embedded object:
db.collection.aggregate(
[
{
$replaceRoot : {
newRoot: {
$mergeObjects: [
{"_id": "$_id"},
"$name"
]
}
}
}
]
)

This may be achieved by using $set to update all documents with the values in the name sub-document:
db.collection.find({ "name": {"$exists": 1 } }).forEach(function(doc) {
var setName = {};
for ( var k in doc.name ) {
setName[k] = doc.name[k];
}
db.collection.update(
{ "_id": doc._id },
{ "$set": setName, "$unset": "name" }
);
})
While I'll recommend you use $project because it would be more performant than this solution, I can understand why you wouldn't want to use $project.

Starting Mongo 4.2, the $replaceWith aggregation operator can be used to replace a document by another (in our case by a sub-document):
// { _id: "1", name: { firstName: "John", lastname: "Peters" } }
// { _id: "2", name: { firstName: "Mary", lastname: "Jones" } }
db.collection.aggregate({ $replaceWith: "$name" })
// { firstName: "John", lastname: "Peters" }
// { firstName: "Mary", lastname: "Jones" }

Related

Add feature from one collection to another in MongoDB using the Aggregation Pipeline

I have two collections users and groups which are related to another. A user can only be in one group, while a group can contain multiple users.
Document A in users
{
_id: 1234,
name: "John Doe"
}
Document B in users
{
_id: 2345,
name: "Jane Roe"
}
Document G in groups
{
_id: 3456,
name: "A Group",
members: [ObjectId("1234"), ObjectId("2345")]
}
Now I want to use the Aggregation Pipeline on the collection users to add the field _group to every user for further processing. The added field should contain the ID of the group the user is part of.
Result for Document A
{
_id: 1234,
name: "John Doe",
_group: ObjectId("3456")
}
Result for Document B
{
_id: 2345,
name: "Jane Roe",
_group: ObjectId("3456")
}
I don't really know where to start and how to combine the two collections in the way I described it.
This should solve the problem (https://mongoplayground.net/p/Iu53HQbi7Me):
Test data:
// users collection
[
{
_id: ObjectId("5a934e000102030405000001"),
name: "John Doe"
},
{
_id: ObjectId("5a934e000102030405000002"),
name: "Jane Roe"
}
]
// groups collection
[
{
_id: 100,
name: "A Group",
members: [
ObjectId("5a934e000102030405000001"),
ObjectId("5a934e000102030405000002")
]
}
]
Query:
db.users.aggregate([
// join the two collections
{
$lookup: {
"from": "groups",
"localField": "_id",
"foreignField": "members",
"as": "membersInfo"
}
},
// unwind the membersInfo array
{
$unwind: "$membersInfo"
},
{
$project: {
"_id": {
$cond: {
"if": {
$in: [
"$_id",
"$membersInfo.members" // replace _id field based on the members
]
},
"then": "$_id",
"else": "No group"
}
},
"name": 1, // mantain this field
"_group": "$membersInfo._id" // create _group field using the _id of groups collection
}
}
])
Result:
[
{
"_group": 100,
"_id": ObjectId("5a934e000102030405000001"),
"name": "John Doe"
},
{
"_group": 100,
"_id": ObjectId("5a934e000102030405000002"),
"name": "Jane Roe"
}
]

Loop inside for loop - nested arrays

I have for loop inside for loop... and my array output is nested array [[{...}]] but I need
[{...}]
Is there any way to make this array without nested array in this case?
Here is my code...
I have users and for every user i need to add new array of objects
users = [{
name: "user1"
role: ["admin", "tester", "viewer"]
},{
name: "user2"
role: ["admin", "tester", "viewer"]
}]
userRole: any[]=[];
for (let user of users){
for (let u of user.role){
this.userRole.push({
name: user.name,
role: u
})
}
this.data.push(this.userRole)
}
User can have multiple role, but because of database structure I need in user object send array with object
{ "name": "user1",
"role": [{
"role" : "admin",
"user" : "user1"
},{
"role" : "tester",
"user" : "user1"
},{
"role" : "view",
"user" : "user1"
}]
}
but I get this
{ "name": "user1",
"role": [ // nested array
[{
"role" : "admin",
"user" : "user1"
},{
"role" : "tester",
"user" : "user1"
},{
"role" : "view",
"user" : "user1"
}]
]
}
I try to use .flat() but still the same
You can simply use two map
let users = [{name: "user1",role: ["admin", "tester", "viewer"]}, {name: "user2",role: ["admin", "tester", "viewer"]}]
let final = users.map(({name,role})=>{
return {
name,
role: role.map(user=>({name,user}))
}
})
console.log(final)
To make your code work, all you need is defined two variable one to hold the final value and another one to hold userRole for particular user,
const users = [{name: "user1",role: ["admin", "tester", "viewer"]}, {name: "user2", role: ["admin", "tester", "viewer"]}]
const final = []
for (let user of users) {
const userRole = [];
for (let u of user.role) {
userRole.push({
name: user.name,
role: u
})
}
final.push({name:user.name, role: userRole})
}
console.log(final)
I made some change on your code. Every loop you are creating a new role, I supposed this.data is a array of userRole? So, you can add it every loop on your data array.
for (let user of users){
for (let u of user.role){
const userRole= { name: user.name, role: u};
this.userRole.push(userRole);
this.data.push(userRole)
}
}
Please use flatMap :
var data = { "name": "user1",
"role": [ // nested array
[{
"role" : "admin",
"user" : "user1"
},{
"role" : "tester",
"user" : "user1"
},{
"role" : "view",
"user" : "user1"
}]
]
};
data.role = data.role.flatMap(data => data);
console.log("print data object");
console.log(data)
users = [{
name: "user1",
role: ["admin", "tester", "viewer"],
},{
name: "user2",
role: ["admin", "tester", "viewer"],
}
]
userRole = users.map(user => {
roles = user.role.map(x => {
return {role:x,user:user.name}
})
return {
name: user.name,
role: roles
}
})
console.log(userRole)
Why don't you use the "u" in the second for loop, to add your array of roles?
Here is the answer, thnx to user #CodeManiac, he gave me a way of thinking...
I need to push item inside second for-loop... and now everything works like I expect
for (let user of users){
for (let u of user.role){
this.data.push(name: user.name, role: u)
}
}

Merge 2 object arrays in json files by key

I'm trying to combine 2 object array in javascript/jquery matching them by the same key (code). These object arrays are stored in 2 separate json files.
I've cut these down as the files are long
Thanks in advance if anyone can help.
Object 1:
[{
"city": "london",
"selfemployed": {
"job" :"Builder",
"code": "abc"
},
"company" : {
"job": "Shopkeeper",
"code": "def"
}
}]
Object 2:
[{
"code": "abc",
"participant": {
"firstname" : "Joe",
"lastname" : "Blogs"
}
},
{
"code": "def",
"participant": {
"firstname" : "Anna",
"lastname" : "Smith"
}
}]
Needed result:
[{
"city": "london",
"selfemployed": {
"job" :"Builder",
"code": "abc",
"participant": {
"firstname" : "Joe",
"lastname" : "Blogs"
}
},
"company" : {
"job": "Shopkeeper",
"code": "def",
"participant": {
"firstname" : "Anna",
"lastname" : "Smith"
}
}
}]
One of my issues is that I'm unable to return the object from the .json files
var file1 = 'url/file1.json';
var file1 = 'url/file2.json';
const joinJson = (file1, file2) => {
$.getJSON(file, function(data1) {
return data1;
});
$.getJSON(file2, function(data2) {
return data2;
});
// do stuff with data1 and data2
}
console.log(joinJson());
You could take a Map and build new objects for the result by selecting the wanted code information for the new object.
This proposal uses rest properties of an object with babel for older user agents.
var cities = [{ city: "london", selfemployed: { job: "Builder", code: "abc" }, company: { job: "Shopkeeper", code: "def" } }],
codes = [{ code: "abc", participant: { firstname: "Joe", lastname: "Blogs" } }, { code: "def", participant: { firstname: "Anna", lastname: "Smith" } }],
codesMap = new Map(codes.map(({ code, participant }) => [code, participant])),
result = cities.map(
({ city, ...items }) =>
Object.assign({ city }, ...Object.entries(items).map(
([k, v]) => ({ [k]: Object.assign({}, v, codesMap.get(v.code)) })
))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The problem is that you're getting the files asynchronously, and returning data in the callback of that async operation, which goes nowhere. Something like this would be better:
var file1 = 'url/file1.json';
var file1 = 'url/file2.json';
const joinJson = (file1, file2) => {
$.getJSON(file, function(data1) {
// async operation 1 complete. Start operation 2.
$.getJSON(file2, function(data2) {
// async operation 2 complete
// data1 and data2 are now available here
console.log("Data 1:", data1);
console.log("Data 2:", data2);
// This will merge the keys of both objects
// but you can replace it with custom merging logic
var merged = Object.assign(data1, data2);
console.log(merged);
});
});
}
The problem here is that you can't do something like console.log(joinJson());. You may very well need something like a Promise.

How to replace property without effecting data in javascript?

I have data in format like this:-
a.arr= [
0:{
name: abc,
dob: 18/12/1917,
panNo: ASDFG6789K
},
1:{
name: xyz,
dob: 1/2/1917,
panNo: WERTY6789K
}
]
I want to remove 0 and 1 and convert it in like this:-
a.arr= [
{
name: abc,
dob: 18/12/2017,
panNo: ASDFG6789K
},
{
name: xyz,
dob: 1/2/1917,
panNo: WERTY6789K
}
]
If I use delete then it removes the data as well inside object. Is there any other way to do this?
As some of the previous commenters said, your first code snippet does not contain valid JavaScript. However, if both the 0 and 1 become strings, as well as all of the values (abc, 18/12/1917, etc.), your data would look like this:
var a = {};
a.arr = [
{
"0": {
name: "abc",
dob: "18/12/1917",
panNo: "ASDFG6789K"
}
},
{
"1": {
name: "xyz",
dob: "1/2/1917",
panNo: "WERTY6789K"
}
}
];
To remove the 0 and 1, you could then do this:
var arr = a.arr.map(function(d){
var key = Object.keys(d)[0];
return d[key];
});
a.arr = arr;
// a.arr is now:
[{
"name": "abc",
"dob": "18/12/1917",
"panNo": "ASDFG6789K"
}, {
"name": "xyz",
"dob": "1/2/1917",
"panNo": "WERTY6789K"
}]

Applying $cond inside subset in Mongodb/Mongoose Aggregation

Lets say I have this document:
{ "_id" : 1, "first_name" : "Matt", "roles": ['editor', 'publisher', 'siteAdmin'] }
{ "_id" : 1, "first_name" : "Harry", "roles": ['publisher', 'siteAdmin'] }
{ "_id" : 1, "first_name" : "Rob", "roles": ['editor'] }
Now I would like to use mongoose aggregation ($cond) to output this document.
How do I get this? I would appreciate your help in this.
{ "first_name" : "Matt", "isSiteAdmin": true }
{ "first_name" : "Harry", "isSiteAdmin": true }
{ "first_name" : "Rob", "isSiteAdmin": false }
A pipeline like the following should work for you
YourModel.aggregate([
{
$project: {
_id: 0,
first_name: 1,
isSiteAdmin: {
$setIsSubset: [["siteAdmin"], "$roles"]
}
}
}
])

Categories

Resources