如何使用 github.com/mitchellh/mapstruct 编码结构,例如:
type Struct struct {
CreateDate *timestamppb.Timestamp `mapstructure:"create_date"`
}
至:
map[string]any{
"create_date": "2024-01-01 12:00:00",
}
我正在尝试类似的事情
func TimestamppbToDatetimeHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f != reflect.TypeOf(×tamppb.Timestamp{}) {
return data, nil
}
return data.(*timestamppb.Timestamp).AsTime().Format(time.DateTime), nil
}
}
还有
m := &Struct{CreateDate: timestamppb.Now()}
data := make(map[string]any)
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &data,
DecodeHook: TimestamppbToDatetimeHookFunc(),
})
if err := d.Decode(m); err != nil {
return nil, err
}
但是d.解码返回错误
'create_date' expected a map, got 'string'
.
也许我可以用
DecodeHookFuncValue (func(from reflect.Value, to reflect.Value) (interface{}, error))
和 Reflect 覆盖输出预期类型,但是如何实现?
DecodeHook
应用于整个输入,而不仅仅是CreateDate
字段,因此f类型是Struct
,而不是时间戳。这应该有效:
func TimestamppbToDatetimeHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f == reflect.TypeOf(&Struct{}) {
v := data.(*Struct)
return map[string]interface{}{
"created_date": v.CreateDate.AsTime().Format(time.DateTime),
}, nil
}
return data, nil
}
}
我感觉
go-viper/mapstructure
并没有很重视这个问题,提出的很多issues
都被忽略了。我选择用我自己的(仅供参考):
package mapstruct
import (
"fmt"
"reflect"
)
func defaultDecoderConfig(config ...*DecoderConfig) *DecoderConfig {
cfg := append(config, &DecoderConfig{})[0]
if cfg.TagName == "" {
cfg.TagName = "json"
}
return cfg
}
type DecoderConfig struct {
DecodeHook func(reflect.Type, any) (any, error)
TagName string
}
func Decode(obj any, config ...*DecoderConfig) (map[string]any, error) {
cfg := defaultDecoderConfig(config...)
result := make(map[string]any)
sv := reflect.ValueOf(obj)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem() // 解引用指针
}
if sv.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected a struct but got %s", sv.Kind())
}
for i, st := 0, sv.Type(); i < sv.NumField(); i++ {
key := st.Field(i).Tag.Get(cfg.TagName)
if key == "" || key == "-" {
continue
}
fv := sv.Field(i) // 获得字段的值
if hook := cfg.DecodeHook; hook != nil {
value, err := hook(fv.Type(), fv.Interface())
if err != nil {
return nil, err
}
result[key] = value
continue
}
switch fv.Kind() {
case reflect.Struct: // 递归处理嵌套结构体
m, err := Decode(fv.Interface())
if err != nil {
return nil, err
}
result[key] = m
default:
result[key] = fv.Interface()
}
}
return result, nil
}
此实现可以将
struct
转换为 map
,如下所示:
type Person struct {
Time time.Time `json:"time"`
Name string `json:"name"`
}
input := Person{time.Now(), "testName"}
m, err := mapstruct.Decode(input, &mapstruct.DecoderConfig{
DecodeHook: func(t reflect.Type, a any) (v any, err error) {
if t == reflect.TypeOf(time.Time{}) {
value, _ := a.(time.Time)
return value.Format(time.DateTime), nil
}
return a, nil
},
})
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", m)
// input => {"name":"testName", "time":"2024-11-22 12:35:55"}