mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 21:32:11 +08:00
feat: allow custom unmarshalers for form binding: json/text/binary
This commit is contained in:
parent
de1c4ec546
commit
26c932b145
@ -197,6 +197,18 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type jsonUnmarshaler interface {
|
||||||
|
UnmarshalJSON([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type textUnmarshaler interface {
|
||||||
|
UnmarshalText([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type binaryUnmarshaler interface {
|
||||||
|
UnmarshalBinary([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
@ -236,6 +248,15 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
|||||||
case time.Time:
|
case time.Time:
|
||||||
return setTimeField(val, field, value)
|
return setTimeField(val, field, value)
|
||||||
}
|
}
|
||||||
|
if unmarshaler, ok := value.Addr().Interface().(jsonUnmarshaler); ok {
|
||||||
|
return unmarshaler.UnmarshalJSON(bytesconv.StringToBytes(val))
|
||||||
|
}
|
||||||
|
if unmarshaler, ok := value.Addr().Interface().(textUnmarshaler); ok {
|
||||||
|
return unmarshaler.UnmarshalText(bytesconv.StringToBytes(val))
|
||||||
|
}
|
||||||
|
if unmarshaler, ok := value.Addr().Interface().(binaryUnmarshaler); ok {
|
||||||
|
return unmarshaler.UnmarshalBinary(bytesconv.StringToBytes(val))
|
||||||
|
}
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -288,3 +290,93 @@ func TestMappingIgnoredCircularRef(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{}, "form")
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this structure has special json unmarshaller, in order to parse email (as an example) as specific structure
|
||||||
|
type withJsonUnmarshaller struct {
|
||||||
|
Name string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withJsonUnmarshaller) UnmarshalJSON(data []byte) error {
|
||||||
|
elems := strings.Split(string(data), "@")
|
||||||
|
if len(elems) != 2 {
|
||||||
|
return errors.New("cannot parse %q as email")
|
||||||
|
}
|
||||||
|
o.Name = elems[0]
|
||||||
|
o.Host = elems[1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingStructFieldJSONUnmarshaller(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Email withJsonUnmarshaller
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test", s.Email.Name)
|
||||||
|
assert.Equal(t, "example.org", s.Email.Host)
|
||||||
|
|
||||||
|
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this structure has special text unmarshaller, in order to parse email (as an example) as specific structure
|
||||||
|
type withTextUnmarshaller struct {
|
||||||
|
Name string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withTextUnmarshaller) UnmarshalText(data []byte) error {
|
||||||
|
elems := strings.Split(string(data), "@")
|
||||||
|
if len(elems) != 2 {
|
||||||
|
return errors.New("cannot parse %q as email")
|
||||||
|
}
|
||||||
|
o.Name = elems[0]
|
||||||
|
o.Host = elems[1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingStructFieldTextUnmarshaller(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Email withTextUnmarshaller
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test", s.Email.Name)
|
||||||
|
assert.Equal(t, "example.org", s.Email.Host)
|
||||||
|
|
||||||
|
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this structure has special binary unmarshaller, in order to parse email (as an example) as specific structure
|
||||||
|
type withBinaryUnmarshaller struct {
|
||||||
|
Name string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withBinaryUnmarshaller) UnmarshalBinary(data []byte) error {
|
||||||
|
elems := strings.Split(string(data), "@")
|
||||||
|
if len(elems) != 2 {
|
||||||
|
return errors.New("cannot parse %q as email")
|
||||||
|
}
|
||||||
|
o.Name = elems[0]
|
||||||
|
o.Host = elems[1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingStructFieldBinaryUnmarshaller(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Email withBinaryUnmarshaller
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test", s.Email.Name)
|
||||||
|
assert.Equal(t, "example.org", s.Email.Host)
|
||||||
|
|
||||||
|
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
54
docs/doc.md
54
docs/doc.md
@ -57,6 +57,7 @@
|
|||||||
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
||||||
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
||||||
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
|
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
|
||||||
|
- [Bind Query with custom unmarshalers](#bind-query-with-custom-unmarshalers)
|
||||||
- [http2 server push](#http2-server-push)
|
- [http2 server push](#http2-server-push)
|
||||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||||
@ -2002,6 +2003,59 @@ func ListHandler(s *Service) func(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bind Query with custom unmarshalers
|
||||||
|
|
||||||
|
Any structure that has custom `UnmarshalJSON` or `UnmarshalText` or `UnmarshalBinary` can be used to parse input as necessary
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
// Booking contains data binded using custom unmarshaler.
|
||||||
|
type Payload struct {
|
||||||
|
Email EmailDetails `form:"email"`
|
||||||
|
}
|
||||||
|
// this structure has special json unmarshaller, in order to parse email (as an example) as specific structure
|
||||||
|
type EmailDetails struct {
|
||||||
|
Name string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
func (o *EmailDetails) UnmarshalJSON(data []byte) error {
|
||||||
|
elems := strings.Split(string(data), "@")
|
||||||
|
if len(elems) != 2 {
|
||||||
|
return fmt.Errorf("cannot parse %q as email", string(data))
|
||||||
|
}
|
||||||
|
o.Name = elems[0]
|
||||||
|
o.Host = elems[1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
route.GET("/email", getEmail)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
func getEmail(c *gin.Context) {
|
||||||
|
var p Payload
|
||||||
|
if err := c.ShouldBindQuery(&p); err == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Email information is correct"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ curl "localhost:8085/email?email=test@example.org"
|
||||||
|
{"message":"Email information is correct"}
|
||||||
|
|
||||||
|
$ curl "localhost:8085/email?email=test-something-else"
|
||||||
|
{"error":"cannot parse \"test-something-else\" as email"}
|
||||||
|
```
|
||||||
|
|
||||||
### http2 server push
|
### http2 server push
|
||||||
|
|
||||||
http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
|
http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user