Skip to content

Conversation

@kankankanp
Copy link

Summary

Addresses issue #2746 by adding support for binding nested structs, arrays, and pointer fields from form data in Echo.
This enhancement allows developers to seamlessly bind complex, deeply nested web form data (such as team.members[0].name=Alice) directly to Go structs with nested slices, pointer fields, and multiple levels of depth.

Changes

  • Implements recursive parsing and binding for form keys using dot notation and array indices (e.g. team.members[0].name).
  • Supports deeply nested structs, slices, and pointer fields within form binding.
  • Updates binder logic to handle keys like winner.players[1].role=Forward and loser.leaders[0].name=Charlie.
  • Adds comprehensive tests for nested, pointer, sparse, and edge-case bindings.

Benefits

  • Enables direct binding of complex HTML form data to Go structs, reducing manual mapping in user applications.
  • Addresses feature requests such as #2746.
  • Maintains backward compatibility with existing flat struct form bindings.
  • Improves ergonomics for web forms with nested data in Echo.

Test plan

  • All new and existing tests pass
  • Linting passes
  • Manual verification with sample Echo server and curl requests
  • No behavioral changes for existing flat form bindings

@aldas aldas self-assigned this Oct 12, 2025
@codecov
Copy link

codecov bot commented Oct 12, 2025

Codecov Report

❌ Patch coverage is 81.60920% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.18%. Comparing base (f24aaff) to head (1555007).
⚠️ Report is 17 commits behind head on master.

Files with missing lines Patch % Lines
bind.go 81.60% 11 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2834      +/-   ##
==========================================
- Coverage   93.25%   93.18%   -0.07%     
==========================================
  Files          39       39              
  Lines        4652     4753     +101     
==========================================
+ Hits         4338     4429      +91     
- Misses        218      225       +7     
- Partials       96       99       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kankankanp
Copy link
Author

@aldas
Could you please approve/enable the workflow run for this PR and re-run CI? I pushed a second commit to fix codecov but the codecov status didn't appear. Thanks.

Copy link
Contributor

@aldas aldas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not very fond of this feature as it is potentially quite complex (in maintenance perspective). I did only rudimentary checks and these index issues make me little bit uneasy.

have you considered creating separate library out of it?

}
case MIMEApplicationXML, MIMETextXML:
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is deleted?

}
}

for key, values := range data {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is also called for header, params and query binding - should it run for them also?

func parseFieldPath(key string) []interface{} {
var parts []interface{}
var buf strings.Builder
for i := 0; i < len(key); i++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be simpler to iterate over key and stop when .[] is encountered cut these parts out of key. part = key[previous:i] this buf parts seems little bit too much.

for ; j < len(key) && key[j] != ']'; j++ {
buf.WriteByte(key[j])
}
index, _ := strconv.Atoi(buf.String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if key is 18446744073709551615 (math.MaxUint64)?
or -1?

I am sure bind will panic with negative numbers

return setWithProperType(fv.Kind(), value, fv)
}
return setValueByParts(fv, ft.Type, parts[1:], value)
case int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these keys should be treated not as indexes but something like grouping key and only allocate so many elements in arrays/slices as there are distinct grouping keys.

just to guard against shenanigans with huge indexes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants