在Go语言开发的API服务场景中,从MongoDB查询得到的文档默认是BSON格式,而前端通常需要JSON格式的数据响应。直接完成两者的转换可以省去大量手动字段映射的工作,提升开发效率。

核心原理说明
MongoDB官方Go驱动返回的文档本质是bson.M或者结构体实例,BSON和JSON在基础数据类型上有天然的对应关系,只需要处理少数特殊类型的转换即可实现直接映射。
常见的对应关系如下:
| BSON类型 | JSON类型 | 说明 |
|---|---|---|
| string | string | 字符串类型直接对应 |
| int32/int64 | number | 整数类型直接对应 |
| double | number | 浮点数类型直接对应 |
| bool | boolean | 布尔类型直接对应 |
| objectid | string | ObjectID需要转换为字符串 | >
| time | string | 时间类型建议转换为ISO格式字符串 |
基础转换实现方式
使用结构体映射转换
如果提前定义好与MongoDB文档对应的结构体,并且给字段添加正确的BSON和JSON标签,就可以直接通过JSON序列化返回响应。
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 定义与MongoDB文档对应的结构体
type User struct {
ID primitive.ObjectID `bson:"_id" json:"id"` // BSON的_id字段映射为JSON的id字段
Name string `bson:"name" json:"name"`
Age int `bson:"age" json:"age"`
Email string `bson:"email" json:"email"`
CreateAt time.Time `bson:"create_at" json:"create_at"`
}
func main() {
// 连接MongoDB
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://127.0.0.1:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
coll := client.Database("test_db").Collection("users")
// 查询单个文档
var user User
err = coll.FindOne(context.Background(), bson.M{"name": "张三"}).Decode(&user)
if err != nil {
log.Fatal(err)
}
// 直接序列化结构体为JSON
jsonData, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
}
使用bson.M直接转换
如果没有提前定义结构体,也可以直接使用bson.M接收文档,再序列化为JSON,这种方式更灵活,适合动态字段的场景。
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// 连接MongoDB
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://127.0.0.1:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
coll := client.Database("test_db").Collection("users")
// 用bson.M接收文档
var result bson.M
err = coll.FindOne(context.Background(), bson.M{"name": "张三"}).Decode(&result)
if err != nil {
log.Fatal(err)
}
// 直接序列化bson.M为JSON
jsonData, err := json.Marshal(result)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
}
特殊场景处理技巧
ObjectID转换问题
默认情况下,primitive.ObjectID序列化后会变成包含时间戳、机器标识等信息的对象,不符合前端预期,需要自定义序列化逻辑。
package main
import (
"encoding/json"
"fmt"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// 自定义ObjectID的JSON序列化逻辑
type CustomObjectID primitive.ObjectID
func (id CustomObjectID) MarshalJSON() ([]byte, error) {
// 转换为十六进制字符串
return json.Marshal(primitive.ObjectID(id).Hex())
}
type User struct {
ID CustomObjectID `bson:"_id" json:"id"`
Name string `bson:"name" json:"name"`
}
func main() {
id := primitive.NewObjectID()
user := User{
ID: CustomObjectID(id),
Name: "李四",
}
data, _ := json.Marshal(user)
fmt.Println(string(data)) // 输出 {"id":"64a1b2c3d4e5f6a7b8c9d0e1f","name":"李四"}
}
时间格式统一
MongoDB中的时间类型默认序列化后是RFC3339格式,如果前端需要其他格式,可以自定义时间字段的序列化规则。
package main
import (
"encoding/json"
"fmt"
"time"
)
// 自定义时间类型,序列化时输出时间戳
type CustomTime time.Time
func (t CustomTime) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(t).Unix())
}
type Article struct {
Title string `bson:"title" json:"title"`
CreateAt CustomTime `bson:"create_at" json:"create_at"`
}
func main() {
article := Article{
Title: "测试文章",
CreateAt: CustomTime(time.Now()),
}
data, _ := json.Marshal(article)
fmt.Println(string(data)) // 输出 {"title":"测试文章","create_at":1692000000}
}
注意事项
- 如果MongoDB文档中包含嵌套文档或者数组,只要结构体定义正确,JSON序列化会自动处理嵌套关系,不需要额外操作。
- 使用
bson.M转换时,如果文档中包含ObjectID类型,序列化后会是对象形式,需要提前处理或者前端做适配。 - 生产环境中建议给JSON响应添加统一的包装结构,比如包含状态码、提示信息、数据字段,方便前端统一处理。
转换过程中如果遇到字段缺失的情况,检查结构体的BSON标签是否和MongoDB文档的字段名完全一致,包括大小写和下划线。