AndaluZ
AndaluZ

Reputation: 1526

How to remove properties of mongo objects in an array?

I hope the snippets below make my question more clearer. I can fetch an object that has embedded an array of category objects. From the category objects I want to show only certain properties.

So this is what I get now:

{
  "photo": [],
  "_id": "5ec55ae39eere8ert85bf8fe749",
  "title": "Blog about coding",
  "description": "Hi, I want to share with you my incredible journey as a software developer...",
  "rate": 3,
  "category": [
    {
      "_id": "5ec55ae39aaa78985f8fe74a",
      "name": "programming",
      "display_name": "Programming"
    }
  ],
  "createdAt": "2020-05-20T16:29:23.982Z",
  "updatedAt": "2020-05-20T16:29:23.982Z",
  "__v": 0
}

With the following code:

/* GET blog with id */
router.get("/:id", async (req, res, next) => {
    try {
        const blog = await Blog.findById(req.params.id); // How to filter out fields of objects in category array?
        res.send(blog);
    } catch(ex) {
        res.status(400).send(ex); 
    }
});

This is what I actually want:

{
  "photo": [],
  "_id": "5ec55ae39eere8ert85bf8fe749",
  "title": "Blog about coding",
  "description": "Hi, I want to share with you my incredible journey as a software developer...",
  "rate": 3,
  "category": [
    {
      "display_name": "Programming"
    }
  ],
  "createdAt": "2020-05-20T16:29:23.982Z",
  "updatedAt": "2020-05-20T16:29:23.982Z",
  "__v": 0
}

So, in category I want only the "display_name"

How can I get this. I thought I saw a solution before, but I can't find it anymore.

Upvotes: 1

Views: 812

Answers (2)

whoami - fakeFaceTrueSoul
whoami - fakeFaceTrueSoul

Reputation: 17915

Since you're using .findById() which is mongoose wrapper to .findOne({_id: ObjectId()}) which offloads burden of converting input string to ObjectId(), but there is nothing much .find()'s can help on projection, You need to list all the fields as like given below :

/** .findById(inputString, projection) */

Blog.findById(req.params.id,
{
  "category.display_name": 1,
  "title": 1,
  "description": 1,
  "rate": 1,
  "createdAt": 1,
  "updatedAt": 1
})

/** Or */

Blog.findById(req.params.id,
{
  "category._id": 0,
  "category.name": 0,
})

Test : As playground can't use .findById test it using .find() : mongoplayground

But if you lean towards using aggregation you can do lot more in projection :

const mongoose = require('mongoose');

/* GET blog with id */
router.get("/:id", async (req, res, next) => {
    try {
        const blog = await Blog.aggregate([{$match : {_id : mongoose.Types.ObjectId(req.params.id)}},
            { $addFields: { category: { $map: { input: "$category", in: { display_name: "$$this.display_name" } } } } }
        ]);
        res.send(blog);
    } catch(ex) {
        res.status(400).send(ex); 
    }
});

Test : mongoplayground

Note : This aggregation query iterates thru category array and keep only display_name in each object, Use this pipeline only if you need to exclude a lot of fields in category objects. I would suggest to use second option in .findById() cause you no need to convert string to ObjectId() and an aggregation pipeline just to exclude fields name and _id inside objects of category array.

Upvotes: 1

mlod_
mlod_

Reputation: 132

You could also create a schema for your Blog collection and then customize the toJSON method for deleting the properties you want to hide like this:

BlogSchema.methods.toJSON = function callback() {

  const blog = this;
  const blogObject = blog.toObject();

  delete blogObject._id;
  delete blogObject.name;

  return blogObject;
};

I use this approach to hide sensible data such as passwords when sending back the users their info!

Upvotes: 2

Related Questions