Golang Echo数据绑定中time.Time类型绑定失败

承诺一时的华丽 · · 1549 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1、首先看官方绑定,time.Time将绑定失败

func(c echo.Context) (err error) {
  u := new(User)
  if err = c.Bind(u); err != nil {
    return
  }
  return c.JSON(http.StatusOK, u)
}

2、自定义绑定

加入Struct类型判断:


image.png

直接添加选项

    case reflect.Struct:
        //时间类型
        var t time.Time
        var err error
        val = strings.Replace(val, " 00:00:00", "", -1)
        
        if IsValidDate(val) {         //判断日期格式
            t, err = ParseDate(val)
            if err == nil {
                structField.Set(reflect.ValueOf(t))
            }
        } else if IsValidTime(val) {         //判断日期时间格式
            t, err = ParseTime(val)
            if err == nil {
                structField.Set(reflect.ValueOf(t))
            }
        }
        break

完整版bind.go

package handle

import (
    "reflect"
    "strconv"
    "strings"
    "github.com/labstack/echo"
    "net/http"
    "encoding/json"
    "fmt"
    "errors"
    "encoding/xml" 
    "time"
)

type CustomBinder struct{}

// Bind implements the `Binder#Bind` function.
func (b *CustomBinder) Bind(i interface{}, c echo.Context) (err error) {
    req := c.Request()
    if req.ContentLength == 0 {
        if req.Method == echo.GET || req.Method == echo.DELETE {
            if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
            return
        }
        return echo.NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
    }
    ctype := req.Header.Get(echo.HeaderContentType)
    switch {
    case strings.HasPrefix(ctype, echo.MIMEApplicationJSON):
        if err = json.NewDecoder(req.Body).Decode(i); err != nil {
            if ute, ok := err.(*json.UnmarshalTypeError); ok {
                return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset))
            } else if se, ok := err.(*json.SyntaxError); ok {
                return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
            } else {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
        }
    case strings.HasPrefix(ctype, echo.MIMEApplicationXML), strings.HasPrefix(ctype, echo.MIMETextXML):
        if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
            if ute, ok := err.(*xml.UnsupportedTypeError); ok {
                return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
            } else if se, ok := err.(*xml.SyntaxError); ok {
                return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
            } else {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
        }
    case strings.HasPrefix(ctype, echo.MIMEApplicationForm), strings.HasPrefix(ctype, echo.MIMEMultipartForm):
        params, err := c.FormParams()
        if err != nil {
            return echo.NewHTTPError(http.StatusBadRequest, err.Error())
        }
        if err = b.bindData(i, params, "form"); err != nil {
            return echo.NewHTTPError(http.StatusBadRequest, err.Error())
        }
    default:
        return echo.ErrUnsupportedMediaType
    }
    return
}

func (b *CustomBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
    typ := reflect.TypeOf(ptr).Elem()
    val := reflect.ValueOf(ptr).Elem()

    if typ.Kind() != reflect.Struct {
        return errors.New("binding element must be a struct")
    }

    for i := 0; i < typ.NumField(); i++ {
        typeField := typ.Field(i)
        structField := val.Field(i)
        if !structField.CanSet() {
            continue
        }
        structFieldKind := structField.Kind()
        inputFieldName := typeField.Tag.Get(tag)

        if inputFieldName == "" {
            inputFieldName = typeField.Name
            // If tag is nil, we inspect if the field is a struct.
            if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
                err := b.bindData(structField.Addr().Interface(), data, tag)
                if err != nil {
                    return err
                }
                continue
            }
        }

        inputValue, exists := data[inputFieldName]
        if !exists {
            // Go json.Unmarshal supports case insensitive binding.  However the
            // url params are bound case sensitive which is inconsistent.  To
            // fix this we must check all of the map values in a
            // case-insensitive search.
            inputFieldName = strings.ToLower(inputFieldName)
            for k, v := range data {
                if strings.ToLower(k) == inputFieldName {
                    inputValue = v
                    exists = true
                    break
                }
            }
        }

        if !exists {
            continue
        }

        // Call this first, in case we're dealing with an alias to an array type
        if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
            if err != nil {
                return err
            }
            continue
        }

        numElems := len(inputValue)
        if structFieldKind == reflect.Slice && numElems > 0 {
            sliceOf := structField.Type().Elem().Kind()
            slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
            for j := 0; j < numElems; j++ {
                if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
                    return err
                }
            }
            val.Field(i).Set(slice)
        } else {
            if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
                return err
            }
        }
    }
    return nil
}

func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
    // But also call it here, in case we're dealing with an array of BindUnmarshalers
    if ok, err := unmarshalField(valueKind, val, structField); ok {
        return err
    }

    switch valueKind {
    case reflect.Ptr:
        return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
    case reflect.Int:
        return setIntField(val, 0, structField)
    case reflect.Int8:
        return setIntField(val, 8, structField)
    case reflect.Int16:
        return setIntField(val, 16, structField)
    case reflect.Int32:
        return setIntField(val, 32, structField)
    case reflect.Int64:
        return setIntField(val, 64, structField)
    case reflect.Uint:
        return setUintField(val, 0, structField)
    case reflect.Uint8:
        return setUintField(val, 8, structField)
    case reflect.Uint16:
        return setUintField(val, 16, structField)
    case reflect.Uint32:
        return setUintField(val, 32, structField)
    case reflect.Uint64:
        return setUintField(val, 64, structField)
    case reflect.Bool:
        return setBoolField(val, structField)
    case reflect.Float32:
        return setFloatField(val, 32, structField)
    case reflect.Float64:
        return setFloatField(val, 64, structField)
    case reflect.String:
        structField.SetString(val)
    case reflect.Struct:
        //时间类型
        var t time.Time
        var err error
        val = strings.Replace(val, " 00:00:00", "", -1)
        if IsValidDate(val) {
            t, err = ParseDate(val)
            if err == nil {
                structField.Set(reflect.ValueOf(t))
            }
        } else if IsValidTime(val) {
            t, err = ParseTime(val)
            if err == nil {
                structField.Set(reflect.ValueOf(t))
            }
        }
        break
    default:
        return errors.New("unknown type")
    }
    return nil
}

func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
    switch valueKind {
    case reflect.Ptr:
        return unmarshalFieldPtr(val, field)
    default:
        return unmarshalFieldNonPtr(val, field)
    }
}

// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
func bindUnmarshaler(field reflect.Value) (echo.BindUnmarshaler, bool) {
    ptr := reflect.New(field.Type())
    if ptr.CanInterface() {
        iface := ptr.Interface()
        if unmarshaler, ok := iface.(echo.BindUnmarshaler); ok {
            return unmarshaler, ok
        }
    }
    return nil, false
}

func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
    if unmarshaler, ok := bindUnmarshaler(field); ok {
        err := unmarshaler.UnmarshalParam(value)
        field.Set(reflect.ValueOf(unmarshaler).Elem())
        return true, err
    }
    return false, nil
}

func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
    if field.IsNil() {
        // Initialize the pointer to a nil value
        field.Set(reflect.New(field.Type().Elem()))
    }
    return unmarshalFieldNonPtr(value, field.Elem())
}

func setIntField(value string, bitSize int, field reflect.Value) error {
    if value == "" {
        value = "0"
    }
    intVal, err := strconv.ParseInt(value, 10, bitSize)
    if err == nil {
        field.SetInt(intVal)
    }
    return err
}

func setUintField(value string, bitSize int, field reflect.Value) error {
    if value == "" {
        value = "0"
    }
    uintVal, err := strconv.ParseUint(value, 10, bitSize)
    if err == nil {
        field.SetUint(uintVal)
    }
    return err
}

func setBoolField(value string, field reflect.Value) error {
    if value == "" {
        value = "false"
    }
    boolVal, err := strconv.ParseBool(value)
    if err == nil {
        field.SetBool(boolVal)
    }
    return err
}

func setFloatField(value string, bitSize int, field reflect.Value) error {
    if value == "" {
        value = "0.0"
    }
    floatVal, err := strconv.ParseFloat(value, bitSize)
    if err == nil {
        field.SetFloat(floatVal)
    }
    return err
}


func ParseTime(date string) (time.Time, error) {
    date = strings.Replace(date, "/", "-", -1)
    date = strings.Replace(date, ".", "-", -1)
    return time.Parse("2006-01-02 15:04:05", date)
}

func ParseDate(date string) (time.Time, error) {
    date = strings.Replace(date, "/", "-", -1)
    date = strings.Replace(date, ".", "-", -1)
    return time.Parse("2006-01-02", date)
}



func IsValidTime(s string) bool {
    _, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return false
    }
    return true
}

func IsValidDate(s string) bool { 
    _, err := time.Parse("2006-01-02", s)
    if err != nil {
        return false
    }
    return true
}

使用示例:

if err := new(CustomBinder).Bind(user, c); err != nil {
        ...
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:承诺一时的华丽

查看原文:Golang Echo数据绑定中time.Time类型绑定失败

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1549 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传