如何将Mongoose关联文档字段移至主文档并返回扁平化DTO
问题
需要将Mongoose中User模型关联的City、Country子文档的cityName、countryName字段迁移至主文档,返回无嵌套结构的单一DTO。以下是相关代码、当前返回结果及期望格式:
示例代码(data.js)
const mongoose = require('mongoose'); //city const citySchema = new mongoose.Schema({ cityName: { type: String, required: true, unique: true }, }); const City = mongoose.model('City', citySchema); //country const countrySchema = new mongoose.Schema({ countryName: { type: String, required: true, unique: true }, }); const Country = mongoose.model('Country', countrySchema); //user const userSchema = new mongoose.Schema({ username: { type: String, required: true }, city: { type: mongoose.Schema.Types.ObjectId, ref: 'City', required: true, }, country: { type: mongoose.Schema.Types.ObjectId, ref: 'Country', required: true, }, createdAt: { type: Date, required: true }, updatedAt: { type: Date, required: true }, }); const User = mongoose.model('User', userSchema); function getUser(id) { return User.findById(id) .populate('city') .populate('country') .exec(); };
当前接口返回JSON
{ "_id": "6321ac3d14a57c2716f7f4a0", "name": "David", "city": { "_id": "63218ce557336b03540c9ce9", "cityName": "New York", "__v": 0 }, "country": { "_id": "632185bbe499d5505cafdcbc", "countryName": "USA", "__v": 0 }, "createdAt": "2022-09-14T10:26:05.000Z", "__v": 0 }
期望返回JSON格式
{ "_id": "6321ac3d14a57c2716f7f4a0", "username": "David", "cityName": "New York", "countryName": "USA", "createdAt": "2022-09-14T10:26:05.000Z", }
解决方案
方法1:查询后手动重组数据
获取populate后的用户文档后,直接提取需要的字段并重组结构:
async function getUser(id) { const user = await User.findById(id) .populate('city') .populate('country') .exec(); return { _id: user._id, username: user.username, cityName: user.city.cityName, countryName: user.country.countryName, createdAt: user.createdAt }; };
这种方式逻辑简单直观,适合字段较少的场景,缺点是需要手动维护字段映射关系。
方法2:结合lean()与投影简化处理
用lean()返回普通JS对象,配合select过滤不必要的字段,再提取嵌套字段到顶层:
async function getUser(id) { return User.findById(id) .populate({ path: 'city', select: 'cityName -_id' // 仅保留cityName,排除_id }) .populate({ path: 'country', select: 'countryName -_id' // 仅保留countryName,排除_id }) .lean() // 提升性能,返回普通JS对象而非Mongoose文档 .then(user => { const { city, country, ...rest } = user; return { ...rest, cityName: city.cityName, countryName: country.countryName }; }); };
lean()能降低内存开销,select减少数据传输量,整体效率更高。
方法3:使用聚合管道在数据库层处理
通过聚合阶段直接在数据库完成字段重组,无需应用层额外计算:
async function getUser(id) { return User.aggregate([ // 匹配目标用户 { $match: { _id: mongoose.Types.ObjectId(id) } }, // 关联City集合,仅获取cityName { $lookup: { from: 'cities', // 注意这里是集合的实际复数名称 localField: 'city', foreignField: '_id', as: 'city' } }, // 关联Country集合,仅获取countryName { $lookup: { from: 'countries', // 集合实际复数名称 localField: 'country', foreignField: '_id', as: 'country' } }, // 展开$lookup返回的数组 { $unwind: '$city' }, { $unwind: '$country' }, // 投影并重命名字段到顶层 { $project: { _id: 1, username: 1, cityName: '$city.cityName', countryName: '$country.countryName', createdAt: 1 } } ]).then(results => results[0]); // 聚合返回数组,取第一个结果 };
聚合管道适合复杂数据重组场景,所有处理在数据库端完成,减少应用层压力。
内容的提问来源于stack exchange,提问作者Howard Hee
相关产品推荐
相关产品推荐

