版本: v1.3.0+
类型: 功能特性
分类: 数据类型处理
从 v1.3.0 版本开始,monSQLize 支持 ObjectId 字符串自动转换 功能。当你在查询条件、更新操作或删除操作中使用 ObjectId 字符串时,monSQLize 会自动将其转换为 MongoDB 的 ObjectId 对象。
核心优势:
- ✅ 简化代码: 无需手动调用
new ObjectId() - ✅ 提升开发效率: 直接使用字符串,代码更简洁
- ✅ 自动识别: 智能判断是否为有效的 ObjectId 字符串
- ✅ 深度转换: 支持嵌套对象和数组中的 ObjectId
- ✅ 安全可控: 支持排除特定字段,防止误转换
const { ObjectId } = require('mongodb');
// ❌ 需要手动转换
const user = await collection('users').findOne({
_id: new ObjectId('507f1f77bcf86cd799439011')
});
// ❌ 查询条件复杂时更繁琐
const posts = await collection('posts').find({
authorId: new ObjectId(userId),
categoryId: new ObjectId(categoryId),
status: 'published'
});
// ❌ 嵌套对象中的转换更麻烦
const result = await collection('orders').updateOne(
{ _id: new ObjectId(orderId) },
{
$set: {
'customer.userId': new ObjectId(customerId),
'items.0.productId': new ObjectId(productId)
}
}
);// ✅ 自动转换,无需手动处理
const user = await collection('users').findOne({
_id: '507f1f77bcf86cd799439011'
});
// ✅ 代码更简洁
const posts = await collection('posts').find({
authorId: userId, // 自动转换
categoryId: categoryId, // 自动转换
status: 'published'
});
// ✅ 嵌套对象也能自动转换
const result = await collection('orders').updateOne(
{ _id: orderId }, // 自动转换
{
$set: {
'customer.userId': customerId, // 自动转换
'items.0.productId': productId // 自动转换
}
}
);monSQLize 会自动将符合以下条件的字符串转换为 ObjectId:
- ✅ 长度为 24 个字符
- ✅ 只包含十六进制字符 (
0-9,a-f,A-F) - ✅ 字段名符合 ObjectId 模式(默认规则)
以下字段名会自动转换:
_id*Id(如userId,postId,categoryId)*_id(如user_id,post_id)*Ids(数组形式,如userIds,postIds)*_ids(数组形式,如user_ids,post_ids)
// ✅ 会转换
{
_id: '507f1f77bcf86cd799439011', // _id
userId: '507f1f77bcf86cd799439011', // *Id
author_id: '507f1f77bcf86cd799439011', // *_id
postIds: ['507f...', '508f...'], // *Ids (数组)
category_ids: ['507f...', '508f...'] // *_ids (数组)
}
// ❌ 不会转换
{
username: 'user123', // 普通字符串
email: 'test@example.com', // 非 ObjectId 格式
code: '1234567890abcdef12345678' // 长度符合但字段名不匹配
}const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
// 配置 ObjectId 自动转换
autoConvertObjectId: {
enabled: true, // 默认启用
// 排除特定字段(不转换)
excludeFields: ['code', 'token'],
// 自定义字段匹配模式
customFieldPatterns: [
/^ref/, // ref 开头的字段
/Reference$/ // Reference 结尾的字段
],
// 最大转换深度(防止循环引用)
maxDepth: 10
}
});| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean | true |
是否启用自动转换 |
excludeFields |
string[] | [] |
排除的字段名列表(不转换) |
customFieldPatterns |
RegExp[] | [] |
自定义字段匹配模式 |
maxDepth |
number | 10 |
最大转换深度(防止循环引用) |
// findOne
const user = await collection('users').findOne({
_id: '507f1f77bcf86cd799439011' // ✅ 自动转换
});
// find
const posts = await collection('posts').find({
authorId: userId, // ✅ 自动转换
categoryId: categoryId // ✅ 自动转换
});
// findById(便利方法)
const product = await collection('products').findOneById(
'507f1f77bcf86cd799439011' // ✅ 自动转换
);// $in 操作符
const users = await collection('users').find({
_id: {
$in: [
'507f1f77bcf86cd799439011',
'507f1f77bcf86cd799439012',
'507f1f77bcf86cd799439013'
] // ✅ 数组中每个元素都会自动转换
}
});
// $or 操作符
const docs = await collection('documents').find({
$or: [
{ authorId: userId1 }, // ✅ 自动转换
{ editorId: userId2 } // ✅ 自动转换
]
});
// 嵌套查询
const orders = await collection('orders').find({
'customer.userId': customerId, // ✅ 嵌套字段自动转换
'items.productId': productId // ✅ 嵌套字段自动转换
});// updateOne
await collection('posts').updateOne(
{ _id: postId }, // ✅ 查询条件自动转换
{
$set: {
authorId: newAuthorId, // ✅ 更新值自动转换
'meta.createdBy': creatorId // ✅ 嵌套字段自动转换
}
}
);
// updateMany
await collection('comments').updateMany(
{ postId: postId }, // ✅ 自动转换
{
$set: {
postId: newPostId // ✅ 自动转换
}
}
);// deleteOne
await collection('users').deleteOne({
_id: userId // ✅ 自动转换
});
// deleteMany
await collection('posts').deleteMany({
authorId: authorId // ✅ 自动转换
});ObjectId 自动转换在以下方法中生效:
- ✅
find(query) - ✅
findOne(query) - ✅
findOneById(id) - ✅
findByIds(ids) - ✅
findPage(options) - ✅
findAndCount(query) - ✅
count(query) - ✅
distinct(field, query)
- ✅
insertOne(doc) - ✅
insertMany(docs) - ✅
updateOne(query, update) - ✅
updateMany(query, update) - ✅
replaceOne(query, doc) - ✅
upsertOne(query, update) - ✅
deleteOne(query) - ✅
deleteMany(query)
- ✅
insertBatch(docs) - ✅
updateBatch(query, update) - ✅
deleteBatch(query)
- ✅
aggregate(pipeline)(在 $match、$lookup 等阶段) - ✅
findOneAndUpdate(query, update) - ✅
findOneAndDelete(query) - ✅
findOneAndReplace(query, doc)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
excludeFields |
string[] | [] |
排除的字段名列表,这些字段不会进行转换 |
customFieldPatterns |
RegExp[] | [] |
自定义字段名正则表达式,匹配的字段会进行转换 |
maxDepth |
number | 10 |
递归转换的最大深度,防止无限递归 |
某些字段虽然符合 ObjectId 格式,但实际上不是 ObjectId:
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
autoConvertObjectId: {
enabled: true,
// 排除这些字段,即使它们看起来像 ObjectId
excludeFields: [
'token', // 认证令牌
'code', // 验证码
'sessionId', // 会话ID(非 MongoDB ObjectId)
'traceId', // 追踪ID
'metadata.externalId', // 嵌套字段也支持
'legacyId' // 遗留系统ID
]
}
});
// 使用示例
await collection('sessions').find({
userId: userId, // ✅ 转换
sessionId: sessionId // ❌ 不转换(在 excludeFields 中)
});注意事项:
excludeFields支持点号路径(如metadata.externalId)- 排除优先级高于默认规则和自定义模式
- 建议明确列出所有非 ObjectId 的
*Id字段
扩展默认的字段匹配规则:
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
autoConvertObjectId: {
enabled: true,
// 自定义字段匹配模式
customFieldPatterns: [
/^.*Ref$/, // 以Ref结尾的字段:userRef, postRef
/^ref/, // ref开头:refUser, refPost
/Reference$/, // Reference结尾:userReference
/^parent/, // parent开头:parentId, parentNode
/^child/, // child开头:childId, childNode
/^related\w+Id$/ // related开头Id结尾:relatedUserId
]
}
});
// 使用示例
await collection('nodes').find({
userRef: userId, // ✅ 转换(匹配 /^.*Ref$/)
refUser: userId, // ✅ 转换(匹配 /^ref/)
userReference: userRefId, // ✅ 转换(匹配 /Reference$/)
parentId: parentId, // ✅ 转换(匹配 /^parent/)
childId: childId, // ✅ 转换(匹配 /^child/)
relatedUserId: relatedId // ✅ 转换(匹配 /^related\w+Id$/)
});自定义模式优先级:
excludeFields- 最高优先级(不转换)customFieldPatterns- 自定义模式- 默认模式(
_id,*Id,*Ids)
防止嵌套过深导致的性能问题:
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
autoConvertObjectId: {
enabled: true,
maxDepth: 5 // 最多递归5层
}
});
// 示例:深度嵌套对象
await collection('complex').find({
level1: { // 深度 1
level2: { // 深度 2
level3: { // 深度 3
level4: { // 深度 4
level5: { // 深度 5
userId: userId // ✅ 深度 5,仍会转换
}
}
}
}
}
});
// ⚠️ 超过 maxDepth 的嵌套不会转换
await collection('deep').find({
level1: { level2: { level3: { level4: { level5: { level6: {
userId: userId // ❌ 深度 6,不会转换
}}}}}}
});深度限制说明:
- 默认
maxDepth = 10,适用于绝大多数场景 - 如果数据结构嵌套很深,建议设置较小值(如 5)
- 超过深度限制的字段不会转换,返回原始值
// 创建测试查询
const query = {
userId: '507f1f77bcf86cd799439011',
sessionId: '507f1f77bcf86cd799439011', // 在 excludeFields 中
metadata: {
externalId: '507f1f77bcf86cd799439011' // 在 excludeFields 中
}
};
// 执行查询(启用日志)
const msq = new MonSQLize({
type: 'mongodb',
config: { uri: '...' },
logger: console, // 启用日志
autoConvertObjectId: {
enabled: true,
excludeFields: ['sessionId', 'metadata.externalId']
}
});
const result = await collection('users').findOne(query);
// 查看转换结果
console.log('userId转换了:', result.userId instanceof ObjectId); // true
console.log('sessionId没转换:', typeof result.sessionId === 'string'); // true
console.log('externalId没转换:', typeof result.metadata.externalId === 'string'); // true// 第三方系统的ID可能是24位十六进制,但不是 ObjectId
autoConvertObjectId: {
enabled: true,
excludeFields: [
'stripeCustomerId', // Stripe客户ID
'paypalOrderId', // PayPal订单ID
'externalSystemId', // 外部系统ID
'legacyUserId' // 遗留系统用户ID
]
}// 租户ID使用自定义格式
autoConvertObjectId: {
enabled: true,
excludeFields: [
'tenantId', // 租户ID(自定义格式)
'organizationId' // 组织ID(自定义格式)
],
customFieldPatterns: [
/^.*ResourceId$/ // 资源ID:userResourceId, fileResourceId
]
}// 限制递归深度,优化性能
autoConvertObjectId: {
enabled: true,
maxDepth: 3, // 只转换3层以内的嵌套
excludeFields: [
'metadata.tracking', // 排除不常用字段
'debug.traceId' // 排除调试字段
]
}ObjectId 自动转换对性能的影响非常小:
- 查询条件转换: <1ms(单次查询)
- 文档插入转换: <1ms(单个文档)
- 批量操作转换: 约 0.1ms/文档
-
避免过深嵌套
- 建议嵌套深度 ≤ 5 层
- 超过 5 层建议扁平化数据结构
-
合理使用 excludeFields
- 排除明确不是 ObjectId 的字段
- 减少不必要的检查
-
批量操作优先
- 使用
insertBatch而非多次insertOne - 批量操作转换效率更高
- 使用
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
autoConvertObjectId: {
enabled: false // 禁用自动转换
}
});或者在实例化后修改(不推荐):
msq.autoConvertConfig.enabled = false;有些字段可能既可以是 ObjectId,也可以是普通字符串:
// 方案 1: 排除该字段(推荐)
autoConvertObjectId: {
excludeFields: ['externalId'] // 不自动转换
}
// 方案 2: 手动判断和转换
const { ObjectId } = require('mongodb');
function isValidObjectIdString(str) {
return typeof str === 'string' && /^[0-9a-fA-F]{24}$/.test(str);
}
const query = {
externalId: isValidObjectIdString(externalId)
? new ObjectId(externalId)
: externalId
};
// 方案 3: 在查询前标准化(推荐用于混合场景)
function normalizeId(id) {
if (isValidObjectIdString(id)) {
return new ObjectId(id);
}
return id;
}
await collection('external').find({
externalId: normalizeId(externalId)
});最佳实践:
- 建议数据模型设计时避免混合类型
- 如果无法避免,优先使用
excludeFields+ 手动转换 - 在应用层统一ID格式,减少类型判断
优先级从高到低:
-
excludeFields(最高优先级)
- 明确排除的字段,即使匹配自定义模式也不转换
-
customFieldPatterns
- 自定义正则模式,优先于默认规则
-
默认模式(最低优先级)
- 内置的
_id,*Id,*Ids,*_id,*_ids规则
- 内置的
// 示例:优先级演示
autoConvertObjectId: {
excludeFields: ['sessionId'], // 最高优先级:不转换
customFieldPatterns: [/Id$/] // 自定义模式:转换以Id结尾的字段
}
await collection('test').find({
userId: '507f...', // ✅ 转换(匹配自定义模式)
sessionId: '507f...', // ❌ 不转换(在 excludeFields 中)
postId: '507f...' // ✅ 转换(匹配默认模式 + 自定义模式)
});不会。ObjectId 转换在查询执行前完成,不影响 MongoDB 查询性能。
转换过程只增加了约 0.1-1ms 的开销,对整体性能影响可以忽略。
可以通过日志查看:
const msq = new MonSQLize({
type: 'mongodb',
databaseName: 'mydb',
config: { uri: '...' },
logger: console, // 启用日志
autoConvertObjectId: {
enabled: true
}
});
// 执行查询时,日志会显示转换信息
await collection('users').findOne({ _id: '507f...' });
// 日志: [DEBUG] ObjectId converted: _id会。包括 $in、$nin 等操作符中的数组:
// ✅ 数组中每个元素都会转换
await collection('users').find({
_id: {
$in: [
'507f1f77bcf86cd799439011',
'507f1f77bcf86cd799439012'
]
}
});
// ✅ 文档中的数组字段也会转换
await collection('posts').insertOne({
authorId: '507f...', // ✅ 转换
tags: ['tag1', 'tag2'], // ❌ 不转换(不是 ObjectId)
relatedIds: ['507f...', ...] // ✅ 转换(字段名匹配)
});最后更新: 2026-01-08
版本: v1.0.6