mongoose-advanced-populate
Version:
Advanced populate logic for Mongoose with nested and flexible path support.
79 lines (67 loc) • 1.67 kB
JavaScript
const mongoose = require('mongoose');
function setNested(doc, path, value) {
const keys = path.split('.');
let current = doc;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (i === keys.length - 1) {
current[key] = value;
} else {
current[key] = current[key] || {};
current = current[key];
}
}
}
const advancedPopulate = async ({ model, match = {}, populates = [] }) => {
const pipeline = [{ $match: match }];
const unwindPaths = new Set(populates.map(p => p.as.split('.')[0]));
for (const path of unwindPaths) {
pipeline.push({
$unwind: {
path: `$${path}`,
preserveNullAndEmptyArrays: true,
},
});
}
for (const populate of populates) {
const {
from,
localField,
foreignField,
as,
pipeline: subPipeline = [],
isArray = true,
} = populate;
const tempField = as.replace(/\./g, '__');
pipeline.push({
$lookup: {
from,
localField,
foreignField,
as: tempField,
pipeline: subPipeline,
},
});
if (!isArray) {
pipeline.push({
$unwind: {
path: `$${tempField}`,
preserveNullAndEmptyArrays: true,
},
});
}
}
const flatResults = await model.aggregate(pipeline);
return flatResults.map(doc => {
const reconstructed = {};
for (const key in doc) {
if (key.includes('__')) {
setNested(reconstructed, key.replace(/__/g, '.'), doc[key]);
} else {
reconstructed[key] = doc[key];
}
}
return reconstructed;
});
};
module.exports = advancedPopulate;