MongoDB作为文档型数据库,同一个集合中的文档结构可能并不一致,某个字段在不同文档里可能是字符串、数字,也可能是数组甚至嵌套文档。在做数据清洗或排查脏数据时,我们往往需要先弄清楚每个字段实际存储的类型。$type操作符正是干这件事的,它既可以在查询中作为匹配条件筛选某种类型的文档,也可以在聚合管道中把字段的类型信息直接输出出来。这篇文章重点讲后者,也就是如何在聚合管道中用$type返回字段的类型标识。

$type在聚合管道中的基本用法
在聚合管道里,$type属于表达式操作符,通常放在$project或$addFields阶段使用。它接收一个字段路径作为参数,返回该字段对应的BSON类型字符串。例如下面的例子中,我们对用户集合的age和name字段分别做类型判断:
db.users.aggregate([
{
$project: {
name: 1,
age: 1,
ageType: { $type: "$age" },
nameType: { $type: "$name" },
_id: 0
}
}
])
假设某个文档的age存储的是整数32,name存储的是字符串,那么输出的ageType就是int,nameType就是string。注意这里的返回值是字符串形式的类型别名,而不是数字编号。返回的类型别名包括double、string、object、array、bool、date、null、int、timestamp、decimal等,完整列表可以查阅官方文档的BSON Types部分。
有一个容易忽略的细节:如果字段在文档中不存在,$type返回的是字符串missing;如果字段存在但值为null,返回的则是null。这两者是完全不同的概念。字段缺失说明该文档根本没有这个键,而null是一个显式存储的值。在排查数据质量问题时,区分这两种情况非常关键,否则你可能会把“没写过这个字段”和“写入了空值”混为一谈,导致统计口径出错。
结合$cond和$switch做类型分支处理
单纯拿到类型字符串往往还不够,实际业务中更常见的需求是“根据类型走不同的处理逻辑”。这时候可以把$type和$cond或$switch配合起来用。比如一个商品价格字段,有的文档存的是数字,有的文档存的是字符串形式的数字,我们希望在聚合时统一转换:
db.products.aggregate([
{
$addFields: {
normalizedPrice: {
$switch: {
branches: [
{
case: { $eq: [{ $type: "$price" }, "string"] },
then: { $toDouble: "$price" }
},
{
case: { $eq: [{ $type: "$price" }, "int"] },
then: { $toDouble: "$price" }
}
],
default: "$price"
}
}
}
}
])
上面这段代码先把字符串和int类型的价格统一转成double,其他类型(比如本身就是double或者decimal)保持原样。$switch的分支条件按顺序匹配,第一个满足的分支生效,都不满足则走default。相比嵌套多层$cond,$switch的可读性明显更好,分支多的时候推荐优先使用。
再举一个数据质检的例子。假设我们要统计用户集合中email字段的各种类型分布,可以这样写:
db.users.aggregate([
{
$group: {
_id: { $type: "$email" },
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } }
])
这个管道按email字段的类型分组计数,一条聚合就能看出有多少文档的email是string、有多少是null、有多少压根没有这个字段。这种写法在异构数据源导入后的验收环节特别实用,比写脚本逐条遍历文档高效得多。
数组与嵌套文档的类型判断技巧
处理数组字段时要格外小心。当字段是数组类型时,$type返回的是array,它反映的是字段本身的类型,而不会深入数组内部判断元素类型。如果你想知道数组内元素的类型分布,需要先$unwind把数组拆开,再对元素做$type判断:
db.orders.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: { $type: "$items" },
count: { $sum: 1 }
}
}
])
需要注意的是,$unwind默认会丢弃items为空数组或字段缺失的文档。如果想把这类文档也统计进来,可以加上preserveNullAndEmptyArrays选项。另外提醒一点历史遗留问题:在旧版本的查询匹配中,$type array匹配的是“数组内含有该类型元素”的文档,这与聚合表达式中$type直接返回array字符串的语义不同,刚接触的同学容易在这里踩坑,建议先在低版本或高版本环境里实际验证一下行为差异。
对于嵌套文档,$type同样只返回外层的object。如果想判断嵌套文档内部某个字段的类型,直接写路径即可,例如{ $type: "$address.city" }。如果路径中间某一层不存在,表达式会返回missing而不是报错,这个设计让多层路径探测变得安全。不过在$group的_id中使用missing值时,它会被当作null处理,统计时可能产生混淆,建议先在$project阶段把missing的情况显式映射成一个标记字符串再分组。
常见坑点与注意事项
第一个坑是数字类型的区分。MongoDB Shell中直接写的数字默认是double类型,只有通过NumberInt或NumberLong包装才是int或long。如果你发现$type返回的永远是double,多半是写入时没有指定类型。在分页展示金额、库存这类需要精确的场景,建议统一用decimal或者写入时明确NumberInt包装,避免类型混乱带来的比较和排序问题。
第二个坑是$type与$isArray的配合。判断数组类型时,除了用$type判断,还有一个专门的$isArray表达式,它返回布尔值,在$cond条件判断里写起来更直观:{ $cond: [{ $isArray: "$tags" }, "是数组", "不是数组"] }。两者选哪个看场景,$type信息更全,$isArray语义更明确。
最后一个建议:$type在$match阶段也可以作为查询操作符使用,写法是{ field: { $type: "string" } },注意这与聚合表达式{ $type: "$field" }的语法完全不同,前者是筛选条件,后者是取值表达式。同一个名字两种用法,混合使用管道时一定要分清楚自己写的是哪一种,否则会直接报表达式语法错误。掌握了这些细节,$type就能成为你处理脏数据和异构文档的得力工具。
MongoDB聚合管道$type操作符字段类型判断修改时间:2026-09-12 23:06:35