Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions form/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func Decode(r *http.Request, dst any) error {
t := reflect.TypeOf(dst)
v := reflect.ValueOf(dst)

if t.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
if t.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
return errors.New("dst must be pointer to struct")
}

Expand Down Expand Up @@ -56,7 +56,7 @@ func decodeForm(dst reflect.Value, form map[string][]string, prefix string) erro
fName := cmp.Or(tagName, field.Name)
fType := field.Type
fKind := fType.Kind()
isPtr := fKind == reflect.Ptr
isPtr := fKind == reflect.Pointer

if prefix != "" && !field.Anonymous {
fName = prefix + "." + fName
Expand All @@ -70,6 +70,28 @@ func decodeForm(dst reflect.Value, form map[string][]string, prefix string) erro
continue
}

if fKind == reflect.Struct {
var found bool

searchPrefix := fName
if field.Anonymous {
searchPrefix = prefix
}
if searchPrefix != "" {
searchPrefix += "."
}

for k := range form {
if searchPrefix == "" || strings.HasPrefix(k, searchPrefix) {
found = true
break
}
}
if !found {
continue
}
}

if fieldVal.IsNil() {
fieldVal.Set(reflect.New(fType))
}
Expand Down Expand Up @@ -105,7 +127,7 @@ func decodeForm(dst reflect.Value, form map[string][]string, prefix string) erro
}
case reflect.Slice:
elemType := fType.Elem()
isPtr := elemType.Kind() == reflect.Ptr
isPtr := elemType.Kind() == reflect.Pointer
baseType := elemType
if isPtr {
baseType = elemType.Elem()
Expand Down
37 changes: 37 additions & 0 deletions form/decode_benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func BenchmarkSimpleDecodeStruct(b *testing.B) {
req, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(values.Encode()))

b.ReportAllocs()
b.ResetTimer()

for n := 0; n < b.N; n++ {
var s Simple

Expand Down Expand Up @@ -80,10 +82,45 @@ func BenchmarkComplexDecodeStruct(b *testing.B) {
req, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(values.Encode()))

b.ReportAllocs()
b.ResetTimer()

for n := 0; n < b.N; n++ {
var c Complex
if err := form.Decode(req, &c); err != nil {
b.Error(err)
}
}
}

func BenchmarkSelfReferencingStruct(b *testing.B) {
type Node struct {
ID string
Name string
Parent *Node
Sibling *Node
Metadata map[string]string
}

values := url.Values{
"ID": []string{"node-1"},
"Name": []string{"Root Node"},
"Parent.ID": []string{"parent-1"},
"Parent.Name": []string{"Parent Node"},
"Sibling.ID": []string{"sibling-1"},
"Sibling.Name": []string{"Sibling Node"},
"Metadata[category]": []string{"test"},
"Metadata[version]": []string{"1.0"},
}

req, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(values.Encode()))

b.ReportAllocs()
b.ResetTimer()

for n := 0; n < b.N; n++ {
var node Node
if err := form.Decode(req, &node); err != nil {
b.Error(err)
}
}
}
Loading
Loading