Get result as an array instead of documents in mongodb for an attribute

I have a User collection with schema

{
     name: String, 
     books: [
         id: { type: Schema.Types.ObjectId, ref: 'Book' } ,
         name: String
     ]


}

Is it possible to get an array of book ids instead of object?

something like:

["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]

Or

{ids: ["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]}

and not

{
    _id: ObjectId("53eb79d863ff0e8229b97448"),
    books:[
        {"id" : ObjectId("53eb797a63ff0e8229b4aca1") }, 
        { "id" : ObjectId("53eb797a63ff0e8229b4acac") }, 
        { "id" : ObjectId("53eb797a63ff0e8229b4acad") }
    ]
}

Currently I am doing

User.findOne({}, {"books.id":1} ,function(err, result){
    var bookIds = [];
    result.books.forEach(function(book){
        bookIds.push(book.id);
    });

});

Is there any better way?

It could be easily done with Aggregation Pipeline, using $unwind and $group.

db.users.aggregate({
  $unwind: '$books'
}, {
  $group: {
    _id: 'books',
    ids: { $addToSet: '$books.id' }
  }
})

the same operation using mongoose Model.aggregate() method:

User.aggregate().unwind('$books').group(
  _id: 'books',
  ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
  // use res[0].ids
})

Note that books here is not a mongoose document, but a plain js object.


You can also add $match to select some part of users collection to run this aggregation query on.

For example, you may select only one particular user:

User.aggregate().match({
  _id: uid
}).unwind('$books').group(
  _id: 'books',
  ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
  // use res[0].ids
})

But if you're not interested in aggregating books from different users into single array, it's best to do it without using $group and $unwind:

User.aggregate().match({
  _id: uid
}).project({
  _id: 0,
  ids: '$books.id'
}).exec(function(err, users) {
  // use users[0].ids
})