mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-12 16:52:29 +08:00
merge main
This commit is contained in:
@@ -11,9 +11,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/structutil"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -235,32 +235,7 @@ func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K
|
|||||||
// map key is specified same as struct field tag `json` value.
|
// map key is specified same as struct field tag `json` value.
|
||||||
// Play: https://go.dev/play/p/KYGYJqNUBOI
|
// Play: https://go.dev/play/p/KYGYJqNUBOI
|
||||||
func StructToMap(value any) (map[string]any, error) {
|
func StructToMap(value any) (map[string]any, error) {
|
||||||
v := reflect.ValueOf(value)
|
return structutil.ToMap(value)
|
||||||
t := reflect.TypeOf(value)
|
|
||||||
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
if t.Kind() != reflect.Struct {
|
|
||||||
return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]any)
|
|
||||||
|
|
||||||
fieldNum := t.NumField()
|
|
||||||
pattern := `^[A-Z]`
|
|
||||||
regex := regexp.MustCompile(pattern)
|
|
||||||
for i := 0; i < fieldNum; i++ {
|
|
||||||
name := t.Field(i).Name
|
|
||||||
tag := t.Field(i).Tag.Get("json")
|
|
||||||
if tag == "" || strings.HasPrefix(tag, "-") || !regex.MatchString(name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tag = strings.Split(tag, ",")[0]
|
|
||||||
result[tag] = v.Field(i).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapToSlice convert map to slice based on iteratee function.
|
// MapToSlice convert map to slice based on iteratee function.
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ func TestToMap(t *testing.T) {
|
|||||||
func TestStructToMap(t *testing.T) {
|
func TestStructToMap(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestStructToMap")
|
assert := internal.NewAssert(t, "TestStructToMap")
|
||||||
|
|
||||||
t.Run("StructToMap", func(t *testing.T) {
|
t.Run("StructToMap", func(_ *testing.T) {
|
||||||
type People struct {
|
type People struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
age int
|
age int
|
||||||
@@ -194,7 +194,7 @@ func TestStructToMap(t *testing.T) {
|
|||||||
assert.Equal(expected, pm)
|
assert.Equal(expected, pm)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("StructToMapWithJsonAttr", func(t *testing.T) {
|
t.Run("StructToMapWithJsonAttr", func(_ *testing.T) {
|
||||||
type People struct {
|
type People struct {
|
||||||
Name string `json:"name,omitempty"` // json tag with attribute
|
Name string `json:"name,omitempty"` // json tag with attribute
|
||||||
Phone string `json:"phone"` // json tag without attribute
|
Phone string `json:"phone"` // json tag without attribute
|
||||||
@@ -202,13 +202,12 @@ func TestStructToMap(t *testing.T) {
|
|||||||
age int // no tag
|
age int // no tag
|
||||||
}
|
}
|
||||||
p := People{
|
p := People{
|
||||||
"test",
|
Phone: "1111",
|
||||||
"1111",
|
Sex: "male",
|
||||||
"male",
|
age: 100,
|
||||||
100,
|
|
||||||
}
|
}
|
||||||
pm, _ := StructToMap(p)
|
pm, _ := StructToMap(p)
|
||||||
var expected = map[string]any{"name": "test", "phone": "1111"}
|
var expected = map[string]any{"phone": "1111"}
|
||||||
assert.Equal(expected, pm)
|
assert.Equal(expected, pm)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -219,7 +218,7 @@ func (client *HttpClient) setTLS(rawUrl string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setHeader set http rquest header
|
// setHeader set http request header
|
||||||
func (client *HttpClient) setHeader(req *http.Request, headers http.Header) {
|
func (client *HttpClient) setHeader(req *http.Request, headers http.Header) {
|
||||||
if headers == nil {
|
if headers == nil {
|
||||||
headers = make(http.Header)
|
headers = make(http.Header)
|
||||||
@@ -278,33 +277,15 @@ func validateRequest(req *HttpRequest) error {
|
|||||||
// StructToUrlValues convert struct to url valuse,
|
// StructToUrlValues convert struct to url valuse,
|
||||||
// only convert the field which is exported and has `json` tag.
|
// only convert the field which is exported and has `json` tag.
|
||||||
// Play: https://go.dev/play/p/pFqMkM40w9z
|
// Play: https://go.dev/play/p/pFqMkM40w9z
|
||||||
func StructToUrlValues(targetStruct any) url.Values {
|
func StructToUrlValues(targetStruct any) (url.Values, error) {
|
||||||
rv := reflect.ValueOf(targetStruct)
|
|
||||||
rt := reflect.TypeOf(targetStruct)
|
|
||||||
|
|
||||||
if rt.Kind() == reflect.Ptr {
|
|
||||||
rt = rt.Elem()
|
|
||||||
}
|
|
||||||
if rt.Kind() != reflect.Struct {
|
|
||||||
panic(fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", targetStruct))
|
|
||||||
}
|
|
||||||
|
|
||||||
result := url.Values{}
|
result := url.Values{}
|
||||||
|
s, err := convertor.StructToMap(targetStruct)
|
||||||
fieldNum := rt.NumField()
|
if err != nil {
|
||||||
pattern := `^[A-Z]`
|
return nil, err
|
||||||
regex := regexp.MustCompile(pattern)
|
}
|
||||||
for i := 0; i < fieldNum; i++ {
|
for k, v := range s {
|
||||||
name := rt.Field(i).Name
|
result.Add(k, fmt.Sprintf("%v", v))
|
||||||
tag := rt.Field(i).Tag.Get("json")
|
|
||||||
// if regex.MatchString(name) && tag != "" && !strings.Contains(tag, "omitempty"){
|
|
||||||
if regex.MatchString(name) && tag != "" {
|
|
||||||
if strings.Contains(tag, "omitempty") {
|
|
||||||
tag = strings.Split(tag, ",")[0]
|
|
||||||
}
|
|
||||||
result.Add(tag, fmt.Sprintf("%v", rv.Field(i).Interface()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,26 +220,26 @@ func TestStructToUrlValues(t *testing.T) {
|
|||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
UserId int `json:"userId"`
|
UserId int `json:"userId"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Status string
|
|
||||||
}
|
}
|
||||||
item1 := TodoQuery{
|
item1 := TodoQuery{
|
||||||
Id: 1,
|
Id: 1,
|
||||||
UserId: 123,
|
UserId: 123,
|
||||||
Name: "test",
|
Name: "",
|
||||||
Status: "completed",
|
}
|
||||||
|
todoValues, err := StructToUrlValues(item1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("params is invalid: %v", err)
|
||||||
}
|
}
|
||||||
queryValues1 := StructToUrlValues(item1)
|
|
||||||
|
|
||||||
assert.Equal("1", queryValues1.Get("id"))
|
assert.Equal("1", todoValues.Get("id"))
|
||||||
assert.Equal("123", queryValues1.Get("userId"))
|
assert.Equal("123", todoValues.Get("userId"))
|
||||||
assert.Equal("test", queryValues1.Get("name"))
|
assert.Equal("", todoValues.Get("name"))
|
||||||
assert.Equal("", queryValues1.Get("status"))
|
|
||||||
|
|
||||||
item2 := TodoQuery{
|
item2 := TodoQuery{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
UserId: 456,
|
UserId: 456,
|
||||||
}
|
}
|
||||||
queryValues2 := StructToUrlValues(item2)
|
queryValues2, _ := StructToUrlValues(item2)
|
||||||
|
|
||||||
assert.Equal("2", queryValues2.Get("id"))
|
assert.Equal("2", queryValues2.Get("id"))
|
||||||
assert.Equal("456", queryValues2.Get("userId"))
|
assert.Equal("456", queryValues2.Get("userId"))
|
||||||
|
|||||||
@@ -134,13 +134,16 @@ func ExampleStructToUrlValues() {
|
|||||||
Name: "test",
|
Name: "test",
|
||||||
Status: "completed",
|
Status: "completed",
|
||||||
}
|
}
|
||||||
queryValues1 := StructToUrlValues(item1)
|
queryValues1, err := StructToUrlValues(item1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
item2 := TodoQuery{
|
item2 := TodoQuery{
|
||||||
Id: 2,
|
Id: 2,
|
||||||
UserId: 456,
|
UserId: 456,
|
||||||
}
|
}
|
||||||
queryValues2 := StructToUrlValues(item2)
|
queryValues2, _ := StructToUrlValues(item2)
|
||||||
|
|
||||||
fmt.Println(queryValues1.Get("id"))
|
fmt.Println(queryValues1.Get("id"))
|
||||||
fmt.Println(queryValues1.Get("userId"))
|
fmt.Println(queryValues1.Get("userId"))
|
||||||
|
|||||||
55
structutil/field.go
Normal file
55
structutil/field.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package structutil
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
value reflect.Value
|
||||||
|
field reflect.StructField
|
||||||
|
tag *Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func newField(v reflect.Value, f reflect.StructField, tagName string) *Field {
|
||||||
|
tag := f.Tag.Get(tagName)
|
||||||
|
return &Field{
|
||||||
|
value: v,
|
||||||
|
field: f,
|
||||||
|
tag: newTag(tag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag returns the value that the key in the tag string.
|
||||||
|
func (f *Field) Tag() *Tag {
|
||||||
|
return f.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the underlying value of the field.
|
||||||
|
func (f *Field) Value() any {
|
||||||
|
return f.value.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmbedded returns true if the given field is an embedded field.
|
||||||
|
func (f *Field) IsEmbedded() bool {
|
||||||
|
return f.field.Anonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExported returns true if the given field is exported.
|
||||||
|
func (f *Field) IsExported() bool {
|
||||||
|
return f.field.PkgPath == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the given field is zero value.
|
||||||
|
func (f *Field) IsZero() bool {
|
||||||
|
z := reflect.Zero(f.value.Type()).Interface()
|
||||||
|
v := f.Value()
|
||||||
|
return reflect.DeepEqual(z, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the given field
|
||||||
|
func (f *Field) Name() string {
|
||||||
|
return f.field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the field's kind
|
||||||
|
func (f *Field) Kind() reflect.Kind {
|
||||||
|
return f.value.Kind()
|
||||||
|
}
|
||||||
88
structutil/struct.go
Normal file
88
structutil/struct.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package structutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultTagName is the default tag for struct fields to lookup.
|
||||||
|
var DefaultTagName = "json"
|
||||||
|
|
||||||
|
// Struct is abstract struct for provide several high level functions
|
||||||
|
type Struct struct {
|
||||||
|
raw any
|
||||||
|
rtype reflect.Type
|
||||||
|
rvalue reflect.Value
|
||||||
|
TagName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new *Struct
|
||||||
|
func New(value any) *Struct {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return &Struct{
|
||||||
|
raw: value,
|
||||||
|
rtype: t,
|
||||||
|
rvalue: v,
|
||||||
|
TagName: DefaultTagName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMap converts the given struct to a map[string]any, where the keys
|
||||||
|
// of the keys are the field names and the values of the map are the values
|
||||||
|
// of the fields. The default map key is the struct field name, but you can
|
||||||
|
// change it. The `json` key is the default tag key. Example:
|
||||||
|
//
|
||||||
|
// // default
|
||||||
|
// Name string `json:"name"`
|
||||||
|
//
|
||||||
|
// // ignore the field
|
||||||
|
// Name string // no tag
|
||||||
|
// Age string `json:"-"` // json ignore tag
|
||||||
|
// sex string // unexported field
|
||||||
|
// Goal int `json:"goal,omitempty"` // omitempty if the field is zero value
|
||||||
|
//
|
||||||
|
// // custom map key
|
||||||
|
// Name string `json:"myName"`
|
||||||
|
//
|
||||||
|
// Only the exported fields of a struct can be converted.
|
||||||
|
func (s *Struct) ToMap() (map[string]any, error) {
|
||||||
|
result := make(map[string]any)
|
||||||
|
|
||||||
|
fields := s.Fields()
|
||||||
|
for _, f := range fields {
|
||||||
|
if !f.IsExported() || f.tag.IsEmpty() || f.tag.Name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f.IsZero() && f.tag.HasOption("omitempty") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: sub struct
|
||||||
|
result[f.tag.Name] = f.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns all the struct fields within a slice
|
||||||
|
func (s *Struct) Fields() []*Field {
|
||||||
|
|
||||||
|
var fields []*Field
|
||||||
|
fieldNum := s.rvalue.NumField()
|
||||||
|
for i := 0; i < fieldNum; i++ {
|
||||||
|
v := s.rvalue.Field(i)
|
||||||
|
sf := s.rtype.Field(i)
|
||||||
|
field := newField(v, sf, DefaultTagName)
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMap convert struct to map, only convert exported struct field
|
||||||
|
// map key is specified same as struct field tag `json` value.
|
||||||
|
func ToMap(v any) (map[string]any, error) {
|
||||||
|
return New(v).ToMap()
|
||||||
|
}
|
||||||
57
structutil/struct_test.go
Normal file
57
structutil/struct_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package structutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/duke-git/lancet/v2/internal"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToMap(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestStructToMap")
|
||||||
|
|
||||||
|
t.Run("StructToMap", func(_ *testing.T) {
|
||||||
|
type People struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
age int
|
||||||
|
}
|
||||||
|
p := People{
|
||||||
|
"test",
|
||||||
|
100,
|
||||||
|
}
|
||||||
|
pm, _ := ToMap(p)
|
||||||
|
var expected = map[string]any{"name": "test"}
|
||||||
|
assert.Equal(expected, pm)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StructToMapWithJsonAttr", func(_ *testing.T) {
|
||||||
|
type People struct {
|
||||||
|
Name string `json:"name,omitempty"` // json tag with attribute
|
||||||
|
Phone string `json:"phone"` // json tag without attribute
|
||||||
|
Sex string `json:"-"` // ignore by "-"
|
||||||
|
Age int // ignore by no tag
|
||||||
|
email string // ignore by unexported
|
||||||
|
IsWorking bool `json:"is_working"`
|
||||||
|
}
|
||||||
|
p1 := People{
|
||||||
|
Name: "AAA", // exist
|
||||||
|
Phone: "1111",
|
||||||
|
Sex: "male",
|
||||||
|
Age: 100,
|
||||||
|
email: "11@gmail.com",
|
||||||
|
}
|
||||||
|
p1m, _ := ToMap(p1)
|
||||||
|
var expect1 = map[string]any{"name": "AAA", "phone": "1111", "is_working": false}
|
||||||
|
assert.Equal(expect1, p1m)
|
||||||
|
|
||||||
|
p2 := People{
|
||||||
|
Name: "",
|
||||||
|
Phone: "2222",
|
||||||
|
Sex: "male",
|
||||||
|
Age: 0,
|
||||||
|
email: "22@gmail.com",
|
||||||
|
IsWorking: true,
|
||||||
|
}
|
||||||
|
p2m, _ := ToMap(p2)
|
||||||
|
var expect2 = map[string]any{"phone": "2222", "is_working": true}
|
||||||
|
assert.Equal(expect2, p2m)
|
||||||
|
})
|
||||||
|
}
|
||||||
32
structutil/tag.go
Normal file
32
structutil/tag.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package structutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/duke-git/lancet/v2/validator"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Name string
|
||||||
|
Options []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTag(tag string) *Tag {
|
||||||
|
res := strings.Split(tag, ",")
|
||||||
|
return &Tag{
|
||||||
|
Name: res[0],
|
||||||
|
Options: res[1:],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tag) HasOption(opt string) bool {
|
||||||
|
for _, o := range t.Options {
|
||||||
|
if o == opt {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tag) IsEmpty() bool {
|
||||||
|
return validator.IsEmptyString(t.Name)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user