mapstructure
mapstructure copied to clipboard
Can StringToTimeHookFunc be enhanced?
I defined an alias type DateTime for time.Time, It mainly implements the time.DateTime format string and time.Time interchange
type DateTime time.Time
func (d DateTime) MarshalJSON() ([]byte, error) {
t := time.Time(d)
if t.Equal(time.Time{}) {
return []byte("null"), nil
}
return []byte(`"` + t.Format(time.DateTime) + `"`), nil
}
func (d *DateTime) UnmarshalJSON(data []byte) error {
str := string(data)
if str == "null" {
*d = DateTime(time.Time{})
} else if date, err := time.Parse(time.DateTime, str); err == nil {
*d = DateTime(date)
} else {
return err
}
return nil
}
func (d DateTime) Value() (driver.Value, error) {
t := time.Time(d)
if t.Equal(time.Time{}) {
return nil, nil
}
return t.Format(time.DateTime), nil
}
func (d *DateTime) Scan(value any) error {
if val, ok := value.(time.Time); ok {
*d = DateTime(val)
return nil
}
return fmt.Errorf("Failed to scan type %T into DateTime", value)
}
Next, I will apply DateTime to the user model and provide a NewModel factory function
type User struct {
Name string `json:"name" gorm:"size:255"`
CreatedAt DateTime `json:"created_at"`
UpdatedAt DateTime `json:"created_at"`
}
func NewModel[T any](attributes map[string]any, tagName string) (T, error) {
var model T
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeHookFunc(time.DateTime),
WeaklyTypedInput: true,
TagName: tagName,
Result: &model,
})
if err != nil {
return model, err
}
return model, decoder.Decode(attributes)
}
When I run, I receive 'created_at' expected a map, got 'string' error message.
attributes := map[string]any {
"name": "foo",
"created_at": "2006-01-02 15:04:05",
"updated_at": "2006-01-02 15:04:05",
}
user, err := NewModel[User](attributes)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("ok")
}
Tracking the code, I found that it can enhance StringToTimeHookFunc to make it more adaptable, e.g.
func StringToTimeHookFunc(layout string) mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
// Rewrite t != reflect.TypeOf(time.Time{}) to ConvertibleTo
if !t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return data, nil
}
// Convert it by parsing
value, err := time.Parse(layout, data.(string))
if err != nil {
return data, err
}
return reflect.ValueOf(value).Convert(t).Interface(), nil
}
}