Skip to content

Commit 1555007

Browse files
committed
Implement nested form binding for structs and arrays, including test cases
1 parent 52d2bff commit 1555007

File tree

2 files changed

+478
-0
lines changed

2 files changed

+478
-0
lines changed

bind.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
302302
return err
303303
}
304304
}
305+
306+
for key, values := range data {
307+
if strings.Contains(key, ".") || strings.Contains(key, "[") {
308+
if err := bindNestedFormField(val, typ, key, values); err != nil {
309+
return err
310+
}
311+
}
312+
}
305313
return nil
306314
}
307315

@@ -487,3 +495,100 @@ func setMultipartFileHeaderTypes(structField reflect.Value, inputFieldName strin
487495

488496
return result
489497
}
498+
499+
func parseFieldPath(key string) []interface{} {
500+
var parts []interface{}
501+
var buf strings.Builder
502+
for i := 0; i < len(key); i++ {
503+
ch := key[i]
504+
switch ch {
505+
case '.':
506+
if buf.Len() > 0 {
507+
parts = append(parts, buf.String())
508+
buf.Reset()
509+
}
510+
case '[':
511+
if buf.Len() > 0 {
512+
parts = append(parts, buf.String())
513+
buf.Reset()
514+
}
515+
j := i + 1
516+
for ; j < len(key) && key[j] != ']'; j++ {
517+
buf.WriteByte(key[j])
518+
}
519+
index, _ := strconv.Atoi(buf.String())
520+
parts = append(parts, index)
521+
buf.Reset()
522+
i = j
523+
default:
524+
buf.WriteByte(ch)
525+
}
526+
}
527+
if buf.Len() > 0 {
528+
parts = append(parts, buf.String())
529+
}
530+
return parts
531+
}
532+
533+
func bindNestedFormField(val reflect.Value, typ reflect.Type, key string, values []string) error {
534+
parts := parseFieldPath(key)
535+
return setValueByParts(val, typ, parts, values[0])
536+
}
537+
538+
func setValueByParts(val reflect.Value, typ reflect.Type, parts []interface{}, value string) error {
539+
if len(parts) == 0 {
540+
return nil
541+
}
542+
part := parts[0]
543+
switch v := part.(type) {
544+
case string:
545+
fieldIdx := -1
546+
for i := 0; i < typ.NumField(); i++ {
547+
ft := typ.Field(i)
548+
if ft.Tag.Get("form") == v || strings.EqualFold(ft.Name, v) {
549+
fieldIdx = i
550+
break
551+
}
552+
}
553+
if fieldIdx == -1 {
554+
return nil
555+
}
556+
fv := val.Field(fieldIdx)
557+
ft := typ.Field(fieldIdx)
558+
if fv.Kind() == reflect.Ptr {
559+
if fv.IsNil() {
560+
fv.Set(reflect.New(ft.Type.Elem()))
561+
}
562+
fv = fv.Elem()
563+
ft.Type = ft.Type.Elem()
564+
}
565+
if len(parts) == 1 {
566+
return setWithProperType(fv.Kind(), value, fv)
567+
}
568+
return setValueByParts(fv, ft.Type, parts[1:], value)
569+
case int:
570+
if val.Kind() != reflect.Slice {
571+
return nil
572+
}
573+
for val.Len() <= v {
574+
val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem())))
575+
}
576+
elem := val.Index(v)
577+
elemType := val.Type().Elem()
578+
579+
if elemType.Kind() == reflect.Ptr {
580+
if elem.IsNil() {
581+
elem.Set(reflect.New(elemType.Elem()))
582+
}
583+
elem = elem.Elem()
584+
elemType = elemType.Elem()
585+
}
586+
587+
if len(parts) == 1 {
588+
return setWithProperType(elem.Kind(), value, elem)
589+
}
590+
591+
return setValueByParts(elem, elemType, parts[1:], value)
592+
}
593+
return nil
594+
}

0 commit comments

Comments
 (0)