https://github.com/xgfone/go-binder
Provide a common binder to bind a value to any, for example, binding a struct to a map.
https://github.com/xgfone/go-binder
bind binder binding bindings decoder go golang struct-binder struct-decoder structure-binder structure-decoder
Last synced: 3 months ago
JSON representation
Provide a common binder to bind a value to any, for example, binding a struct to a map.
- Host: GitHub
- URL: https://github.com/xgfone/go-binder
- Owner: xgfone
- License: apache-2.0
- Created: 2023-04-02T09:22:44.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-07-24T13:29:37.000Z (about 1 year ago)
- Last Synced: 2024-10-30T20:49:04.198Z (12 months ago)
- Topics: bind, binder, binding, bindings, decoder, go, golang, struct-binder, struct-decoder, structure-binder, structure-decoder
- Language: Go
- Homepage:
- Size: 56.6 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Go Binder [](https://github.com/xgfone/go-binder/actions/workflows/go.yml) [](https://pkg.go.dev/github.com/xgfone/go-binder) [](https://raw.githubusercontent.com/xgfone/go-binder/master/LICENSE)
Provide a common binder to bind a value to any, for example, binding a struct to a map.
For the struct, the package registers `github.com/xgfone/go-structs.Reflect` to reflect the fields of struct to validate the struct value. And, `github.com/xgfone/go-validation.Validate` may be used to validate each field value of the struct based on the built rule.
## Install
```shell
$ go get -u github.com/xgfone/go-binder
```## Example
### Bind the basic types
```go
package mainimport (
"fmt"
"reflect"
"time""github.com/xgfone/go-binder"
)func main() {
type (
IntT int
UintT uint
FloatT float64
StringT string
)var (
Bool bool
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Uint64 uint64
Float32 float32
Float64 float64
String stringintT IntT
uintT UintT
floatT FloatT
stringT StringT
)println := func(dst, src interface{}) {
err := binder.Bind(dst, src)
fmt.Println(reflect.ValueOf(dst).Elem().Interface(), err)
}println(&Bool, "true")
println(&Int, time.Second)
println(&Int8, 11.0)
println(&Int16, "12")
println(&Int32, true)
println(&Int64, time.Unix(1672531200, 0))
println(&Uint, 20)
println(&Uint8, 21.0)
println(&Uint16, "22")
println(&Uint32, true)
println(&Uint64, 23)
println(&Float32, "1.2")
println(&Float64, 30)
println(&String, 40)println(&intT, 50.0)
println(&uintT, IntT(60))
println(&floatT, StringT("70"))
println(&stringT, "test")// Output:
// true
// 1000
// 11
// 12
// 1
// 1672531200
// 20
// 21
// 22
// 1
// 23
// 1.2
// 30
// 40
// 50
// 60
// 70
// test
}
```### Bind the struct
```go
package mainimport (
"fmt"
"time""github.com/xgfone/go-binder"
)func main() {
type (
Int int
Uint uint
String string
Float float64
)var S struct {
Bool bool
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Uint64 uint64
Float32 float32
Float64 float64
String stringDuration1 time.Duration
Duration2 time.Duration
Duration3 time.Duration
Time1 time.Time
Time2 time.TimeEmbed struct {
Int1 int
Int2 IntUint1 uint
Uint2 UintString1 string
String2 StringFloat1 float64
Float2 Float
}Ignore string `json:"-"`
Squash struct {
Field1 int
Field2 int
} `json:",squash"`
}maps := map[string]interface{}{
"Bool": true,"Int": 10,
"Int8": 11,
"Int16": 12,
"Int32": 13,
"Int64": 14,"Uint": 20,
"Uint8": 21,
"Uint16": 22,
"Uint32": 23,
"Uint64": 24,"Float32": 30,
"Float64": 31,"String": "abc",
"Duration1": "1s", // string => time.Duration
"Duration2": 2000, // int(ms) => time.Duration
"Duration3": 3.0, // float(s) => time.Duration
"Time1": 1672531200, // int(unix timestamp) => time.Time
"Time2": "2023-02-01T00:00:00Z", // string(RFC3339) => time.Time"Embed": map[string]interface{}{
"Int1": "41", // string => int
"Int2": "42", // string => Int
"Uint1": "43", // string => uint
"Uint2": "44", // string => Uint
"Float1": "45", // string => float64
"Float2": "46", // string => Float
"String1": 47, // int => string
"String2": 48, // int => String
},"Ignore": "xyz",
"Field1": 51,
"Field2": 52,
}err := binder.Bind(&S, maps)
if err != nil {
fmt.Println(err)
return
}fmt.Printf("Bool=%v\n", S.Bool)
fmt.Printf("Int=%v\n", S.Int)
fmt.Printf("Int8=%v\n", S.Int8)
fmt.Printf("Int16=%v\n", S.Int16)
fmt.Printf("Int32=%v\n", S.Int32)
fmt.Printf("Int64=%v\n", S.Int64)
fmt.Printf("Uint=%v\n", S.Uint)
fmt.Printf("Uint8=%v\n", S.Uint8)
fmt.Printf("Uint16=%v\n", S.Uint16)
fmt.Printf("Uint32=%v\n", S.Uint32)
fmt.Printf("Uint64=%v\n", S.Uint64)
fmt.Printf("Float32=%v\n", S.Float32)
fmt.Printf("Float64=%v\n", S.Float64)
fmt.Printf("String=%v\n", S.String)
fmt.Printf("Duration1=%v\n", S.Duration1)
fmt.Printf("Duration2=%v\n", S.Duration2)
fmt.Printf("Duration3=%v\n", S.Duration3)
fmt.Printf("Time1=%v\n", S.Time1.Format(time.RFC3339))
fmt.Printf("Time2=%v\n", S.Time2.Format(time.RFC3339))
fmt.Printf("Embed.Int1=%v\n", S.Embed.Int1)
fmt.Printf("Embed.Int2=%v\n", S.Embed.Int2)
fmt.Printf("Embed.Uint1=%v\n", S.Embed.Uint1)
fmt.Printf("Embed.Uint2=%v\n", S.Embed.Uint2)
fmt.Printf("Embed.String1=%v\n", S.Embed.String1)
fmt.Printf("Embed.String2=%v\n", S.Embed.String2)
fmt.Printf("Embed.Float1=%v\n", S.Embed.Float1)
fmt.Printf("Embed.Float2=%v\n", S.Embed.Float2)
fmt.Printf("Squash.Field1=%v\n", S.Squash.Field1)
fmt.Printf("Squash.Field2=%v\n", S.Squash.Field2)
fmt.Printf("Ignore=%v\n", S.Ignore)// Output:
// Bool=true
// Int=10
// Int8=11
// Int16=12
// Int32=13
// Int64=14
// Uint=20
// Uint8=21
// Uint16=22
// Uint32=23
// Uint64=24
// Float32=30
// Float64=31
// String=abc
// Duration1=1s
// Duration2=2s
// Duration3=3s
// Time1=2023-01-01T00:00:00Z
// Time2=2023-02-01T00:00:00Z
// Embed.Int1=41
// Embed.Int2=42
// Embed.Uint1=43
// Embed.Uint2=44
// Embed.String1=47
// Embed.String2=48
// Embed.Float1=45
// Embed.Float2=46
// Squash.Field1=51
// Squash.Field2=52
// Ignore=
}
```### Bind the containers
```go
package mainimport (
"fmt"
"net/url""github.com/xgfone/go-binder"
)func main() {
type Ints []int
var S struct {
Maps map[string]interface{} `json:"maps"`
Slices []string `json:"slices"`
Structs []struct {
Ints Ints `json:"ints"`
Query url.Values `json:"query"`
} `json:"structs"`
}maps := map[string]interface{}{
"maps": map[string]string{"k11": "v11", "k12": "v12"},
"slices": []interface{}{"a", "b", "c"},
"structs": []map[string]interface{}{
{
"ints": []string{"21", "22"},
"query": map[string][]string{
"k20": {"v21", "v22"},
"k30": {"v31", "v32"},
},
},
{
"ints": []int{31, 32},
"query": map[string][]string{
"k40": {"v40"},
},
},
},
}err := binder.Bind(&S, maps)
if err != nil {
fmt.Println(err)
return
}fmt.Printf("Maps: %v\n", S.Maps)
fmt.Printf("Slices: %v\n", S.Slices)
for i, s := range S.Structs {
fmt.Printf("Structs[%d]: Ints=%v, Query=%v\n", i, s.Ints, s.Query)
}// Output:
// Maps: map[k11:v11 k12:v12]
// Slices: [a b c]
// Structs[0]: Ints=[21 22], Query=map[k20:[v21 v22] k30:[v31 v32]]
// Structs[1]: Ints=[31 32], Query=map[k40:[v40]]
}
```### Bind the interfaces
```go
package mainimport (
"errors"
"fmt"
"strconv""github.com/xgfone/go-binder"
)// Int is the customized int.
type Int int// Set implements the interface binder.Setter.
func (i *Int) Set(src interface{}) (err error) {
switch v := src.(type) {
case int:
*i = Int(v)
case string:
var _v int64
_v, err = strconv.ParseInt(v, 10, 64)
if err == nil {
*i = Int(_v)
}
default:
err = fmt.Errorf("unsupport to convert %T to Int", src)
}
return
}func (i Int) String() string {
return fmt.Sprint(int64(i))
}// Struct is the customized struct.
type Struct struct {
Name string
Age Int
}// UnmarshalBind implements the interface binder.Unmarshaler.
func (s *Struct) UnmarshalBind(src interface{}) (err error) {
if maps, ok := src.(map[string]interface{}); ok {
s.Name, _ = maps["Name"].(string)
err = s.Age.Set(maps["Age"])
return
}
return fmt.Errorf("unsupport to convert %T to a struct", src)
}func (s Struct) String() string {
return fmt.Sprintf("Name=%s, Age=%d", s.Name, s.Age)
}func main() {
var iface1 Int
var iface2 Struct
var S = struct {
Interface1 binder.Setter
Interface2 binder.UnmarshalerInterface3 error
Interface4 *errorInterface5 interface{} // Use to store any type value.
// binder.Unmarshaler // Do not use the anonymous interface.
}{
Interface1: &iface1, // For interface, must be set to a pointer
Interface2: &iface2, // to an implementation.
}iface3 := errors.New("test1")
iface4 := errors.New("test2")
maps := map[string]interface{}{
"Interface1": "123",
"Interface2": map[string]interface{}{"Name": "Aaron", "Age": 18},
"Interface3": iface3,
"Interface4": iface4,
"Interface5": "any",
}err := binder.Bind(&S, maps)
if err != nil {
fmt.Println(err)
return
}fmt.Printf("Interface1: %v\n", S.Interface1)
fmt.Printf("Interface2: %v\n", S.Interface2)
fmt.Printf("Interface3: %v\n", S.Interface3)
fmt.Printf("Interface4: %v\n", *S.Interface4)
fmt.Printf("Interface5: %v\n", S.Interface5)// Output:
// Interface1: 123
// Interface2: Name=Aaron, Age=18
// Interface3: test1
// Interface4: test2
// Interface5: any
}
```### Hook
```go
package mainimport (
"fmt"
"mime/multipart"
"reflect""github.com/xgfone/go-binder"
)func main() {
src := map[string][]*multipart.FileHeader{
"file": {{Filename: "file"}},
"files": {{Filename: "file1"}, {Filename: "file2"}},
}var dst struct {
File *multipart.FileHeader `json:"file"`
Files []*multipart.FileHeader `json:"files"`
}// (xgf) By default, the binder cannot bind *multipart.FileHeader
// to []*multipart.FileHeader. However, we can use hook to do it.
// Here, there are two ways to finish it:
// 1. We just convert []*multipart.FileHeader to *multipart.FileHeader,
// then let the binder continue to finish binding.
// 2. We finish the binding in the hook.
// 3. Set ConvertSliceToSingle to true to enable the auto-conversion.
// In the exmaple, we use the first.
multiparthook := func(dst reflect.Value, src interface{}) (interface{}, error) {
if _, ok := dst.Interface().(*multipart.FileHeader); !ok {
return src, nil // Let the binder continue to handle it.
}srcfiles, ok := src.([]*multipart.FileHeader)
if !ok {
return src, nil // Let the binder continue to handle it.
} else if len(srcfiles) == 0 {
return nil, nil // FileHeader is empty, we tell the binder not to do it.
}
return srcfiles[0], nil
}err := binder.Binder{Hook: multiparthook}.Bind(&dst, src)
if err != nil {
fmt.Println(err)
return
}fmt.Printf("File.Filename=%s\n", dst.File.Filename)
for i, file := range dst.Files {
fmt.Printf("Files[%d].Filename=%s\n", i, file.Filename)
}// Output:
// File.Filename=file
// Files[0].Filename=file1
// Files[1].Filename=file2
}
```### Bind HTTP Request Body
```go
func main() {
http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {
var body struct {
Field int `json:"field"`
// ...
}
err := binder.BodyDecoder.Decode(&body, r)
// ...
})
}
```### Bind HTTP Request Query
```go
func main() {
http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {
var query struct {
Field int `query:"field"`
// ...
}
err := binder.QueryDecoder.Decode(&query, r)
// ...
})
}
```### Bind HTTP Request Header
```go
func main() {
http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {
var header struct {
Field int `header:"x-field"` // or "X-Field"
// ...
}
err := binder.HeaderDecoder.Decode(&header, r)
// ...
})
}
```