Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Can StringToTimeHookFunc be enhanced? #330

Open
kaysonwu opened this issue May 11, 2023 · 0 comments
Open

Can StringToTimeHookFunc be enhanced? #330

kaysonwu opened this issue May 11, 2023 · 0 comments

Comments

@kaysonwu
Copy link

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
	}
}
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant