Skip to content

Commit

Permalink
[+] feat: 添加对 slice 和 array 的支持
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-M-C committed Jul 8, 2021
1 parent b2db350 commit 40d0693
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 32 deletions.
156 changes: 129 additions & 27 deletions marshal.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,32 +25,34 @@ func marshalToValues(in interface{}) (kv url.Values, err error) {
fv := v.Field(i) // field value fv := v.Field(i) // field value
ft := t.Field(i) // field type ft := t.Field(i) // field type


if ft.Anonymous { readFieldToKV(&fv, &ft, kv)
// TODO: 后文再处理 }
continue
}
if !fv.CanInterface() {
continue
}


tg := readTag(&ft, "url") return kv, nil
if tg.Name() == "-" { }
continue
}


str, ok := readFieldVal(&fv, tg) func readFieldToKV(fv *reflect.Value, ft *reflect.StructField, kv url.Values) {
if !ok { if ft.Anonymous {
continue numField := fv.NumField()
} for i := 0; i < numField; i++ {
if str == "" && tg.Has("omitempty") { ffv := fv.Field(i)
continue fft := ft.Type.Field(i)

readFieldToKV(&ffv, &fft, kv)
} }
return
}
if !fv.CanInterface() {
return
}


// 写 KV 值 tg := readTag(ft, "url")
kv.Set(tg.Name(), str) if tg.Name() == "-" {
return
} }


return kv, nil // 写 KV 值
readFieldValToKV(fv, tg, kv)
} }


func validateMarshalParam(in interface{}) (v reflect.Value, err error) { func validateMarshalParam(in interface{}) (v reflect.Value, err error) {
Expand Down Expand Up @@ -84,21 +86,121 @@ func validateMarshalParam(in interface{}) (v reflect.Value, err error) {
return return
} }


func readFieldVal(v *reflect.Value, tag tags) (s string, ok bool) { func readFieldValToKV(v *reflect.Value, tg tags, kv url.Values) {
key := tg.Name()
val := ""
var vals []string
omitempty := tg.Has("omitempty")
isSliceOrArray := false

switch v.Type().Kind() { switch v.Type().Kind() {
default: default:
return "", false omitempty = true
case reflect.String: case reflect.String:
return v.String(), true val = v.String()
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
return strconv.FormatInt(v.Int(), 10), true val = strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
return strconv.FormatUint(v.Uint(), 10), true val = strconv.FormatUint(v.Uint(), 10)
case reflect.Bool: case reflect.Bool:
return fmt.Sprintf("%v", v.Bool()), true val = fmt.Sprintf("%v", v.Bool())
case reflect.Float64, reflect.Float32: case reflect.Float64, reflect.Float32:
return strconv.FormatFloat(v.Float(), 'f', -1, 64), true val = strconv.FormatFloat(v.Float(), 'f', -1, 64)

case reflect.Slice, reflect.Array:
isSliceOrArray = true
elemTy := v.Type().Elem()
switch elemTy.Kind() {
default:
// 什么也不做,omitempty 对数组而言没有意义
case reflect.String:
vals = readStringArray(v)
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
vals = readIntArray(v)
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
vals = readUintArray(v)
case reflect.Bool:
vals = readBoolArray(v)
case reflect.Float64, reflect.Float32:
vals = readFloatArray(v)
}
}

// 数组使用 Add 函数
if isSliceOrArray {
for _, v := range vals {
kv.Add(key, v)
}
return
}

if val == "" && omitempty {
return
}
kv.Set(key, val)
}

func readStringArray(v *reflect.Value) (vals []string) {
count := v.Len()

for i := 0; i < count; i++ {
child := v.Index(i)
s := child.String()
vals = append(vals, s)
} }

return
}

func readIntArray(v *reflect.Value) (vals []string) {
count := v.Len()

for i := 0; i < count; i++ {
child := v.Index(i)
v := child.Int()
vals = append(vals, strconv.FormatInt(v, 10))
}

return
}

func readUintArray(v *reflect.Value) (vals []string) {
count := v.Len()

for i := 0; i < count; i++ {
child := v.Index(i)
v := child.Uint()
vals = append(vals, strconv.FormatUint(v, 10))
}

return
}

func readBoolArray(v *reflect.Value) (vals []string) {
count := v.Len()

for i := 0; i < count; i++ {
child := v.Index(i)
if child.Bool() {
vals = append(vals, "true")
} else {
vals = append(vals, "false")
}
}

return
}

func readFloatArray(v *reflect.Value) (vals []string) {
count := v.Len()

for i := 0; i < count; i++ {
child := v.Index(i)
v := child.Float()
vals = append(vals, strconv.FormatFloat(v, 'f', -1, 64))
}

return
} }


type tags []string type tags []string
Expand Down
93 changes: 88 additions & 5 deletions marshal_test.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
"strconv"
"testing" "testing"


. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
Expand Down Expand Up @@ -75,11 +76,18 @@ func testMarshal(t *testing.T) {
Name string `url:",omitempty"` Name string `url:",omitempty"`
Sex int Sex int
} }
type Dog struct {
Pet
Color string `url:"color,omitempty"`
}


p := Pet{ p := Dog{
OwnerID: "tencent", Pet: Pet{
Name: "Penguin", OwnerID: "tencent",
Sex: 1, Name: "Penguin",
Sex: 1,
},
Color: "black",
} }


s, err := Marshal(&p) s, err := Marshal(&p)
Expand All @@ -89,8 +97,83 @@ func testMarshal(t *testing.T) {


u, err := url.ParseQuery(string(s)) u, err := url.ParseQuery(string(s))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(u), ShouldEqual, 3) So(len(u), ShouldEqual, 4)
So(u.Get("owner_id"), ShouldEqual, p.OwnerID) So(u.Get("owner_id"), ShouldEqual, p.OwnerID)
So(u.Get("Name"), ShouldEqual, p.Name) So(u.Get("Name"), ShouldEqual, p.Name)
So(u.Get("Sex"), ShouldEqual, fmt.Sprintf("%d", p.Sex)) So(u.Get("Sex"), ShouldEqual, fmt.Sprintf("%d", p.Sex))
So(u.Get("color"), ShouldEqual, p.Color)
}

func testMarshalSlice(t *testing.T) {
type thing struct {
Strings []string `url:"strings"`
Ints []int `url:"ints"`
Uints []uint `url:"uints"`
Bools []bool `url:"bools"`
Floats []float32 `url:"floats"`
Empty []int `url:"empty"`
HexArray [16]int8 `url:"hex_array"`
}

th := &thing{
Strings: []string{"A", "B", "C"},
Ints: []int{-1, -3, -5},
Uints: []uint{2, 4, 6},
Bools: []bool{true, false, true, false},
Floats: []float32{1.1, -2.2, 3.3},
HexArray: [16]int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF},
}

kv, err := marshalToValues(th)
So(err, ShouldBeNil)

t.Logf("result: %s", kv.Encode())

strLst := kv["strings"]
So(len(strLst), ShouldEqual, len(th.Strings))
for i, s := range strLst {
So(s, ShouldEqual, th.Strings[i])
}

intLst := kv["ints"]
So(len(intLst), ShouldEqual, len(th.Ints))
for i, s := range intLst {
n, err := strconv.ParseInt(s, 10, 64)
So(err, ShouldBeNil)
So(n, ShouldEqual, th.Ints[i])
}

uintLst := kv["uints"]
So(len(uintLst), ShouldEqual, len(th.Uints))
for i, s := range uintLst {
n, err := strconv.ParseUint(s, 10, 64)
So(err, ShouldBeNil)
So(n, ShouldEqual, th.Uints[i])
}

boolLst := kv["bools"]
So(len(boolLst), ShouldEqual, len(th.Bools))
for i, s := range boolLst {
So(s, ShouldEqual, fmt.Sprint(th.Bools[i]))
}

floatLst := kv["floats"]
So(len(floatLst), ShouldEqual, len(th.Floats))
for i, s := range floatLst {
f, err := strconv.ParseFloat(s, 32)
So(err, ShouldBeNil)
So(f, ShouldEqual, th.Floats[i])
}

hexArray := kv["hex_array"]
So(len(hexArray), ShouldEqual, len(th.HexArray))
for i, s := range hexArray {
n, err := strconv.ParseInt(s, 10, 64)
So(err, ShouldBeNil)
So(n, ShouldEqual, th.HexArray[i])
}

_, exist := kv["empty"]
So(exist, ShouldBeFalse)

} }
1 change: 1 addition & 0 deletions urlquery_test.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ func test(t *testing.T, scene string, f func(*testing.T)) {
func TestSlice(t *testing.T) { func TestSlice(t *testing.T) {
test(t, "marshalToValues", testMarshalToValues) test(t, "marshalToValues", testMarshalToValues)
test(t, "Marshal", testMarshal) test(t, "Marshal", testMarshal)
test(t, "Marshal slice", testMarshalSlice)
} }

0 comments on commit 40d0693

Please sign in to comment.