Go mongo-driver结构体合并后MongoDB存为键值数组问题及bson标签疑问
问题:MongoDB存储结构体时出现键值对数组而非嵌套对象的问题
我们代码库中有一个合并结构体的函数CombineStruct,实现如下:
func CombineStruct(s1 interface{}, s2 interface{}) error { data, err := json.Marshal(s1) if err != nil { return err } return json.Unmarshal(data, s2) }
使用方式:
m := model.SomeModel{} CombineStruct(someStruct, &m) // 合并两个结构体
目前所有结构体仅带有json标签,示例结构体如下(注意Field3的标签存在笔误):
type someStruct struct { Field1 string `json:"field1"` Field2 string `json:"field2"` Field3 interface{} `json:"field2"` // 此处标签应为"field3" }
切换到go.mongodb.org/mongo-driver/mongo驱动后,合并结构体写入MongoDB时,数据呈现为键值对数组形式:
"studentDetails" : [ { "Key" : "Details", "Value" : [ [ { "Key" : "Name", "Value" : "Bob" }, { "Value" : "21", "Key" : "Age" } ] ] }, { "Key" : "Enrolled", "Value" : false } ],
但我们期望数据以嵌套对象形式存储:
"studentDetails" : { "Details" : [ { "name" : "serverdr", "age" : 21 }, { "Enrolled" : false } ] }
此前使用旧版globalsign/mgo驱动时数据可正常呈现为嵌套对象,请问如何解决该问题?是否需要添加bson标签?
解决方案
1. 必须添加bson标签
旧版mgo驱动会默认 fallback 使用json标签作为bson字段名,但mongo-driver严格区分json和bson标签:
- 没有
bson标签时,驱动会使用结构体字段的原名(首字母大写) - 对于
interface{}类型字段,缺少bson标签会导致驱动无法正确识别字段类型,进而序列化为键值对数组
修正后的结构体需添加与json标签一致的bson标签:
type someStruct struct { Field1 string `json:"field1" bson:"field1"` Field2 string `json:"field2" bson:"field2"` Field3 interface{} `json:"field3" bson:"field3"` // 修正标签笔误 }
2. 替换CombineStruct的实现逻辑
原函数通过json.Marshal/Unmarshal合并结构体,会丢失原结构体的类型信息:
- 原结构体字段会被序列化为
map[string]interface{} mongo-driver处理无类型约束的map时,会将其序列化为键值对数组而非嵌套对象
改用反射直接合并结构体字段,保留原类型信息:
import ( "fmt" "reflect" ) func CombineStruct(s1 interface{}, s2 interface{}) error { v1 := reflect.ValueOf(s1) v2 := reflect.ValueOf(s2).Elem() // 校验输入类型 if v1.Kind() != reflect.Struct { return fmt.Errorf("s1 must be a struct") } if v2.Kind() != reflect.Struct { return fmt.Errorf("s2 must be a pointer to a struct") } t1 := v1.Type() for i := 0; i < t1.NumField(); i++ { srcField := t1.Field(i) dstField := v2.FieldByName(srcField.Name) // 跳过目标结构体中不存在或不可设置的字段 if !dstField.IsValid() || !dstField.CanSet() { continue } // 仅合并源结构体中非零值的字段(可选逻辑,可根据需求调整) if !reflect.Value.IsZero(v1.Field(i)) { dstField.Set(v1.Field(i)) } } return nil }
3. 修正结构体标签笔误
原示例中Field3的json标签写成了field2,这会导致字段覆盖,必须修正为对应字段名的标签。
内容的提问来源于stack exchange,提问作者Gopher_k
相关产品推荐
相关产品推荐

