Skip to content

fix(binding): handle non-JSON string values in form binding for custom struct types#4585

Open
mango766 wants to merge 1 commit intogin-gonic:masterfrom
mango766:fix/form-binding-json-unmarshal-string
Open

fix(binding): handle non-JSON string values in form binding for custom struct types#4585
mango766 wants to merge 1 commit intogin-gonic:masterfrom
mango766:fix/form-binding-json-unmarshal-string

Conversation

@mango766
Copy link

Summary

Fixes #2510

When a struct field implements json.Unmarshaler and is used with form binding (e.g. application/x-www-form-urlencoded), the form value is passed directly to json.Unmarshal. Since form values are plain strings (like 2020/09/23 13:20:49), not valid JSON, the unmarshal fails with an error like invalid character '/' after top-level value.

This PR adds a fallback: if the initial json.Unmarshal fails, it retries with the value wrapped in double quotes, treating it as a JSON string. This lets custom types implementing json.Unmarshaler (e.g. custom datetime parsers) work correctly with form binding, while keeping existing JSON-based form values working as before.

Changes

  • Extract unmarshalFieldAsJSON helper in binding/form_mapping.go that handles the quote-and-retry logic
  • Apply it to both reflect.Struct and reflect.Map fallback paths in setWithProperType
  • Add tests for custom json.Unmarshaler struct types with form binding

Pull Request Checklist

  • Open your pull request against the master branch.
  • All tests pass in available continuous integration systems (e.g., GitHub Actions).
  • Tests are added or modified as needed to cover code changes.
  • If the pull request introduces a new feature, the feature is documented in the docs/doc.md. (Not applicable — this is a bug fix)

…m struct types

When a struct field implements json.Unmarshaler, form binding passes the
raw form value directly to json.Unmarshal. Since form values are plain
strings (e.g. "2020/09/23 13:20:49"), not valid JSON, the unmarshal
fails. Fix this by retrying with the value wrapped in JSON quotes when
the initial unmarshal fails.

Fixes gin-gonic#2510
@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.37%. Comparing base (3dc1cd6) to head (cb5463e).
⚠️ Report is 275 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4585      +/-   ##
==========================================
- Coverage   99.21%   98.37%   -0.84%     
==========================================
  Files          42       48       +6     
  Lines        3182     3145      -37     
==========================================
- Hits         3157     3094      -63     
- Misses         17       42      +25     
- Partials        8        9       +1     
Flag Coverage Δ
?
--ldflags="-checklinkname=0" -tags sonic 98.36% <100.00%> (?)
-tags go_json 98.30% <100.00%> (?)
-tags nomsgpack 98.35% <100.00%> (?)
go-1.18 ?
go-1.19 ?
go-1.20 ?
go-1.21 ?
go-1.25 98.37% <100.00%> (?)
go-1.26 98.37% <100.00%> (?)
macos-latest 98.37% <100.00%> (-0.84%) ⬇️
ubuntu-latest 98.37% <100.00%> (-0.84%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

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.

FormPost will binding failed of custom type in some case with json.Unmarshal

1 participant