Skip to content

Commit

Permalink
Support for interface type
Browse files Browse the repository at this point in the history
  • Loading branch information
sonh committed Oct 7, 2020
1 parent a9c6547 commit 389a80f
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 31 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# qs #
![build](https://github.com/sonh/qs/workflows/build/badge.svg?branch=main)
[![codecov](https://codecov.io/gh/sonh/qs/branch/main/graph/badge.svg)](https://codecov.io/gh/sonh/qs)
[![GoReportCard](https://goreportcard.com/badge/github.com/sonh/qs)](https://goreportcard.com/report/github.com/sonh/qs)
[![release](https://img.shields.io/github/release/sonh/qs.svg)](https://github.com/sonh/qs/releases/)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/sonh/qs)](https://pkg.go.dev/github.com/sonh/qs)
[![GoReportCard](https://goreportcard.com/badge/github.com/sonh/qs)](https://goreportcard.com/report/github.com/sonh/qs)
[![GoVersionOfGoMod](https://img.shields.io/github/go-mod/go-version/sonh/qs.svg)](https://github.com/sonh/qs)
[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sonh/qs/blob/main/LICENSE)

Package sonh/qs encodes structs into url.Values.

Expand Down Expand Up @@ -167,7 +167,7 @@ fmt.Println(values.Encode()) //(unescaped) output: "user[from]=1601623397728&use
```

### Limitation
- `interface`\, `[]interface`\, `map` are not supported yet
- `map` is not supported yet
- `struct`, `slice/array` multi-level nesting are limited
- no decoder yet

Expand Down
7 changes: 6 additions & 1 deletion encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ func (e *encoder) encodeStruct(stVal reflect.Value, values url.Values, scope []b
// skip field
continue
case *listField:
if cachedFld.cachedField == nil {
//data type is not supported
continue
}
if cachedFld.arrayFormat <= arrayFormatBracket {
// With cachedFld type is slice/array, only accept non-nil value
for stFldVal.Kind() == reflect.Ptr {
Expand All @@ -169,6 +173,7 @@ func (e *encoder) encodeStruct(stVal reflect.Value, values url.Values, scope []b
values[cachedFld.name] = make([]string, 0, stFldVal.Len())
}
}

// format value
cachedFld.formatFnc(stFldVal, func(name string, val string) {
values[name] = append(values[name], val)
Expand Down Expand Up @@ -237,7 +242,7 @@ func (e *encoder) structCaching(fields *cachedFields, stVal reflect.Value, scope
}
*fields = append(*fields, e.newListField(elemType, e.tags[0], e.tags[1:]))
default:
*fields = append(*fields, e.newCachedFieldByKind(fieldTyp.Kind(), e.tags[0], e.tags[1:]))
*fields = append(*fields, newCachedFieldByKind(fieldTyp.Kind(), e.tags[0], e.tags[1:]))
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions encode_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@ type (
cachedFields []cachedField
)

func (e *encoder) newCacheFieldByType(typ reflect.Type, tagName []byte, tagOptions [][]byte) cachedField {
func newCacheFieldByType(typ reflect.Type, tagName []byte, tagOptions [][]byte) cachedField {
switch typ {
case timeType:
return newTimeField(tagName, tagOptions)
default:
return e.newCachedFieldByKind(typ.Kind(), tagName, tagOptions)
return newCachedFieldByKind(typ.Kind(), tagName, tagOptions)
}
}

func (e *encoder) newCachedFieldByKind(kind reflect.Kind, tagName []byte, tagOptions [][]byte) cachedField {
func newCachedFieldByKind(kind reflect.Kind, tagName []byte, tagOptions [][]byte) cachedField {
switch kind {
case reflect.String:
return newStringField(tagName, tagOptions)
Expand All @@ -69,7 +69,9 @@ func (e *encoder) newCachedFieldByKind(kind reflect.Kind, tagName []byte, tagOpt
case reflect.Complex128:
return newComplex128Field(tagName, tagOptions)
case reflect.Struct:
return newEmbedField(0, e.tags[0], e.tags[1:])
return newEmbedField(0, tagName, tagOptions)
case reflect.Interface:
return newInterfaceField(tagName, tagOptions)
default:
return nil
}
Expand Down
7 changes: 2 additions & 5 deletions encode_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ func TestCacheStore(t *testing.T) {

func TestNewCacheField(t *testing.T) {
test := assert.New(t)
e := NewEncoder()

name := []byte(`abc`)
opts := [][]byte{[]byte(`omitempty`)}

cacheField := e.dataPool.Get().(*encoder).newCachedFieldByKind(reflect.ValueOf("").Kind(), name, opts)
cacheField := newCachedFieldByKind(reflect.ValueOf("").Kind(), name, opts)
if stringField, ok := cacheField.(*stringField); ok {
test.Equal(string(name), stringField.name)
test.True(stringField.omitEmpty)
Expand All @@ -42,9 +40,8 @@ func TestNewCacheField(t *testing.T) {

func TestNewCacheField2(t *testing.T) {
test := assert.New(t)
e := NewEncoder()

var strPtr *string
cacheField := e.dataPool.Get().(*encoder).newCachedFieldByKind(reflect.ValueOf(strPtr).Kind(), nil, nil)
cacheField := newCachedFieldByKind(reflect.ValueOf(strPtr).Kind(), nil, nil)
test.Nil(cacheField)
}
65 changes: 54 additions & 11 deletions encode_field.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,11 @@ type listField struct {
}

func (listField *listField) formatFnc(field reflect.Value, result resultFunc) {

switch listField.arrayFormat {
case arrayFormatComma:
var str strings.Builder
for i := 0; i < field.Len(); i++ {
if listField.cachedField == nil {
return
}
elemVal := field.Index(i)
for elemVal.Kind() == reflect.Ptr {
elemVal = elemVal.Elem()
Expand All @@ -101,9 +99,6 @@ func (listField *listField) formatFnc(field reflect.Value, result resultFunc) {
result(listField.name, str.String())
case arrayFormatRepeat, arrayFormatBracket:
for i := 0; i < field.Len(); i++ {
if listField.cachedField == nil {
return
}
elemVal := field.Index(i)
for elemVal.Kind() == reflect.Ptr {
elemVal = elemVal.Elem()
Expand All @@ -118,10 +113,6 @@ func (listField *listField) formatFnc(field reflect.Value, result resultFunc) {
case arrayFormatIndex:
count := 0
for i := 0; i < field.Len(); i++ {
if listField.cachedField == nil {
return
}

elemVal := field.Index(i)
for elemVal.Kind() == reflect.Ptr {
elemVal = elemVal.Elem()
Expand Down Expand Up @@ -167,7 +158,7 @@ func (e *encoder) newListField(elemTyp reflect.Type, tagName []byte, tagOptions
}

listField := &listField{
cachedField: e.newCacheFieldByType(elemTyp, nil, tagOptions),
cachedField: newCacheFieldByType(elemTyp, nil, tagOptions),
}

for _, tagOption := range tagOptions {
Expand Down Expand Up @@ -577,3 +568,55 @@ func newCustomField(tagName []byte, tagOptions [][]byte, formatter func(val inte
formatter: formatter,
}
}

type interfaceField struct {
*baseField
tagName []byte
tagOptions [][]byte
fieldMap map[reflect.Type]cachedField
}

func (interfaceField *interfaceField) formatFnc(v reflect.Value, result resultFunc) {

v = v.Elem()

for v.Kind() == reflect.Ptr {
v = v.Elem()
}

if !v.IsValid() {
if !interfaceField.omitEmpty {
result(interfaceField.name, "")
}
return
}

if field := interfaceField.fieldMap[v.Type()]; field == nil {
interfaceField.fieldMap[v.Type()] = newCacheFieldByType(v.Type(), interfaceField.tagName, interfaceField.tagOptions)
}
if field := interfaceField.fieldMap[v.Type()]; field != nil {
field.formatFnc(v, result)
}
}

func newInterfaceField(tagName []byte, tagOptions [][]byte) *interfaceField {
copiedTagName := make([]byte, len(tagName))
copy(copiedTagName, tagName)
copiedTagOptions := make([][]byte, len(tagOptions))
copy(copiedTagOptions, tagOptions)

field := &interfaceField{
baseField: &baseField{
name: string(copiedTagName),
},
tagName: copiedTagName,
tagOptions: copiedTagOptions,
fieldMap: make(map[reflect.Type]cachedField, 5),
}
for _, tagOption := range tagOptions {
if string(tagOption) == tagOmitEmpty {
field.omitEmpty = true
}
}
return field
}
45 changes: 38 additions & 7 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,18 +825,49 @@ func TestEncodeCustomType(t *testing.T) {
test.Equal(expected, values)
}

func TestEncoderIgnoreUnregisterType(t *testing.T) {
func TestEncodeInterface(t *testing.T) {
test := assert.New(t)
encoder := NewEncoder()

type newStr string

s := &struct {
newStr newStr `qs:"new_str"`
newStrList []newStr
String interface{} `qs:"string"`
Bool interface{} `qs:"bool"`
Int interface{} `qs:"int"`
EmptyFloat interface{} `qs:"float,omitempty"`
NilPtr interface{} `qs:"nil_ptr"`
OmitNilPtr interface{} `qs:"omit_nil_ptr,omitempty"`
}{
String: "abc",
Bool: true,
Int: withInt(5),
NilPtr: nil,
}

values, err := encoder.Values(&s)
test.NoError(err)

expected := url.Values{
"string": []string{"abc"},
"bool": []string{"true"},
"int": []string{"5"},
"nil_ptr": []string{""},
}
test.Equal(expected, values)
}

func TestEncoderIgnoreUnregisterType(t *testing.T) {
test := assert.New(t)
encoder := NewEncoder()

s := struct {
Fn []func() `qs:"fn,bracket"`
Ch []chan struct{} `qs:"chan,comma"`
Ch2 []chan struct{} `qs:"chan2,index"`
Ch3 []chan struct{} `qs:"chan3"`
}{
newStr: "abc",
newStrList: []newStr{newStr("a")},
Fn: []func(){func() {}},
Ch: []chan struct{}{make(chan struct{})},
Ch2: []chan struct{}{make(chan struct{})},
}

values, err := encoder.Values(s)
Expand Down

0 comments on commit 389a80f

Please sign in to comment.