To include/exclude properties from read operation result, you need to use $projection.
Currently (in MongoDB v4.2), there is not built-in mechanism, that would allow you to conditionally project (include/exclude) fields, based the props, that are in your query, so you will have to build the projection object on the application side.
Let me show you on example (every example below works fine in mongo-shell).
Imagine we have this dataset:
db.test1.insertMany([
{
a: 'x1',
y: 'y1',
z: 'z1',
}
]);
Then we have 3 functions somewhere in our application:
- to build projection object
function calcProjectionForQuery(query) {
// by default include only document ids in the output
let projection = { _id: true };
// get list of queried properties
const keysAsked = Object.keys(query);
// include queried properties to the output
keysAsked.forEach(key => {
projection[key] = true;
});
// add additional fields to the query output,
// based on queried properties
if (keysAsked.includes('x') && keysAsked.includes('y')) {
projection.z = true;
}
return projection;
}
- to run a query, using that projection object
function runQuery(query) {
const projection = calcProjectionForQuery(query);
return db.test1.find(query, projection);
}
- to run an aggregation, using that projection object (if aggregations are used in your project)
function runAggregation(query) {
const projection = calcProjectionForQuery(query);
return db.test1.aggregate([
{
$match: query,
},
{
$project: projection,
}
]);
}
Execution results:
> runQuery({ x: 'x1' })
{ "_id" : ObjectId("..."), "x" : "x1" }
> runQuery({ y: 'y1' })
{ "_id" : ObjectId("..."), "y" : "y1" }
> runQuery({ x: 'x1', y: 'y1' })
{ "_id" : ObjectId("..."), "x" : "x1", "y" : "y1", "z" : "z1" }
> runQuery({})
{ "_id" : ObjectId("...") }
>
You can achieve the same, if you would store that mapping object in the database. It may look like this:
db.test2.insertMany([
{
queryName: 'query1',
variants: [
{
keysAsked: ['x', 'y'],
addKeysToProjection: ['z'],
},
{
keysAsked: ['y', 'z'],
addKeysToProjection: ['x'],
}
]
},
{
queryName: 'query2',
variants: [
{
keysAsked: ['a', 'b'],
addKeysToProjection: ['c'],
},
],
},
]);
But, the projection building mechanism can be even more complex, than on the example above, because:
- You need to take care, that those mapping objects exist in your databases (local, dev, preprod, prod) in actual state (so, you may need update your databases, if your code changes)
- You need to handle the situations, if someone accidentally deletes/modifies those objects if using shared db
You will also need to plan those mappings fetching strategy:
- fetch it when application starts (app will not be able to use latest mappings, until next restart-refetch)
- fetch it with every request (can drastically increase the number of your read-requests to db)
- fetch it with interval (additional logic)
Summary
It is much more rational and easier to store projection mappings and build projection objects with your application code.
PS: There is also $redact aggregation pipeline stage, that allows you to include/exclude nested objects or entire documents, based on your conditions.