使用 bson:",inline" 可将嵌入结构体字段扁平化存储至父文档层级,避免默认的两层嵌套;必须显式声明 bson 标签而非 json 标签,且被 inline 的结构体不能有同名字段,否则覆盖。

用 bson:",inline" 消除嵌套层级
Go 结构体里嵌入另一个结构体时,不加标签会导致 MongoDB 存成两层嵌套文档,比如 {"user": {"name": "Alice", "age": 30}}。加上 bson:",inline" 后,字段直接提升到父级:{"name": "Alice", "age": 30}。
常见错误是只在 struct 字段上写了 json:",inline" 却忘了 bson 标签——Go 的 mgo 或 mongo-go-driver 完全不认 json 标签做存储映射。
- 必须显式写
bson:",inline",不能省略引号或逗号 - 被 inline 的结构体不能有同名字段,否则会覆盖(如两个结构体都有
ID字段) - 如果用的是
mongo-go-driver,注意它不支持inline的旧版语法,需改用bson:"-,inline"或手动展开字段
把动态键名转为固定数组字段
当原始 JSON 中用运动项目名("Athletics"、"Swimming")作 key,且值类型不一致(有时是数组,有时是带 "Men"/"Women" 的对象),直接映射会导致 Go 反序列化失败或前端反复 typeof 判断。
正确做法是提前在应用层或聚合管道里统一收口:用 $objectToArray 把键值对转成数组,再用 $unwind + $replaceRoot 提升字段,最终输出固定结构的 SubCategory 数组。
- 避免在 schema 层保留
"Athletics": [...]这类字段——MongoDB 无法对动态 key 建索引 - 如果必须保留原始字段,可用
db.collection.createIndex({"Sport": 1, "SubCategory.Gender": 1})加速按项目+性别查 - 前端不再需要
if (data.Swimming && typeof data.Swimming === 'object')这类防御性判断
用 ETL 工具做导入时的扁平化预处理
从 CSV 或外部 JSON 导入 MongoDB 时,mongoimport 本身不支持字段重命名或结构转换。若源数据含 {"address": {"city": "NYC", "zip": "10001"}},又不想存成嵌套文档,就得先处理。
推荐用 JoltTransform(NiFi 内置)或 jq 做清洗:把 address.city 映射为顶层 city,address.zip 映射为 zip,再喂给 mongoimport。
-
mongoimport --type csv --headerline默认所有字段都是字符串,数字/布尔不会自动转换 - 如果 CSV 里有双引号包裹的逗号(如
"123 Main St, Apt 4B"),必须确保导出时已正确转义,否则字段错位 - 不要依赖
mongoimport --columnsHaveTypes自动推断类型——它只识别极简格式,遇到"true"可能仍存为字符串
关系型数据库映射时别硬塞宽表
把 MongoDB 文档转 MySQL 时,有人习惯把整个 SubCategory 数组 JSON 字符串塞进一个 TEXT 字段,或者暴力展开成 50 列(subcat_1_name, subcat_1_gender, …)。这两种方式都会让查询和维护变脆弱。
真正可扩展的做法是建子表:主表存 sport 和基础字段,子表用 sport_id 外键关联,每行存一个 SubCategory 条目。这样既能加索引,也能轻松增删条目。
- 如果历史数据量大,迁移时用
$unwind配合$lookup生成中间扁平化视图,再导出到关系库 - 避免在子表里冗余存储重复字段(如每个子项都存一遍
sport名),靠外键关联更节省空间且一致性可控 - 前端分页查某项目的全部子类时,SQL 只需
SELECT * FROM subcategories WHERE sport_id = ?,不用解析 JSON 字符串
最常被忽略的一点:扁平化不是为了“看起来整齐”,而是为了让每个字段具备明确语义、可索引、可验证、可被不同语言安全消费。一旦开始用 $objectToArray 或 bson:",inline",就要同步更新所有读写路径——包括聚合管道、Go 结构体、前端接口定义,否则很快就会出现“这个字段在 A 地方是数组,在 B 地方是对象”的混乱。











