Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant