Skip to content

Add BindJSONBody to bind the request body value kin-openapi validated - #165

Merged
mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:feat/bind-json-body
Oct 23, 2026
Merged

mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:feat/bind-json-body

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

Summary

Adds runtime.BindJSONBody(r *http.Request, v any) error and runtime.BindJSONValue(value any, v any) error, for the strict net/http server template in oapi-codegen/oapi-codegen#2605.

Strict net/http servers decode a JSON request body a second time after the validation middleware has validated it. encoding/json matches member names case-insensitively, so {"role":"user","ROLE":"admin"} passes validation as role: user and reaches the handler as Role: "admin", bypassing the schema's constraints.

kin-openapi v0.151.0 added openapi3filter.Options.KeepValidatedValue, which leaves the decoded and validated value on r.Body behind a ValidatedValue() any method.

  • BindJSONBody binds that value into the request struct when it is present, and otherwise (or when the value does not fit the destination) falls back to json.NewDecoder(r.Body).Decode(v), so error behaviour, including io.EOF for an empty body, is unchanged.
  • BindJSONValue does the binding by reflection, following encoding/json's rules (struct tags, embedded struct promotion and dominance, json.Unmarshaler / encoding.TextUnmarshaler, base64 []byte, float64 for any, ,string), with one deliberate difference: member names are matched exactly. The destination is left untouched when binding fails.

No new dependencies; go.mod stays at Go 1.24.

Tests

  • A table test binds the same documents through encoding/json and through BindJSONValue into a struct covering every field shape oapi-codegen generates (scalars, pointers, slices, arrays, maps with string and int keys, any, json.RawMessage, time.Time, types.Date, types.UUID, types.Email, nullable.Nullable, embedded structs, json:"-", ,string) and asserts identical results, including null handling.
  • Error cases assert that both reject the same inputs and that the destination is untouched.
  • BindJSONBody tests cover the validated path, the plain path, the fallback after a failed bind (body still readable) and io.EOF.
  • Passes with both the Go 1.27 encoding/json (json/v2-backed) and GOEXPERIMENT=nojsonv2.

🤖 Generated with Claude Code

Strict net/http servers decode a JSON request body a second time after the
validation middleware has validated it, and encoding/json matches member
names case-insensitively, so a body such as {"role":"user","ROLE":"admin"}
passes validation as role=user and reaches the handler as Role="admin",
bypassing the schema's constraints.

kin-openapi v0.151.0 added openapi3filter.Options.KeepValidatedValue, which
leaves the decoded and validated value on the request body behind a
ValidatedValue() any method. BindJSONBody binds that value into the request
struct when it is present, and otherwise (or when the value does not fit the
destination) falls back to json.NewDecoder(r.Body).Decode as before, so
error behaviour is unchanged.

BindJSONValue does the binding by reflection, following encoding/json's rules
for struct tags, embedded struct promotion, json.Unmarshaler and
encoding.TextUnmarshaler, base64 []byte and float64 for any, with exact
member-name matching. The destination is left untouched when binding fails.

Part of oapi-codegen/oapi-codegen#2605.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mromaszewicz

Copy link
Copy Markdown
Member Author

Consumed by oapi-codegen/oapi-codegen#2612, which switches the strict net/http template to runtime.BindJSONBody.

golangci-lint flagged two things in the bindJSONAll fixture struct:

- staticcheck SA5008 on the `json:"-,"` tag. That tag is intentional: it
  names the member "-" (the encoding/json documented way to do so), and
  TestBindJSONValue_FieldCacheIsStable asserts the binder resolves it that
  way. Keep the tag and silence the check with an explanatory nolint.
- unused on the `unexported` field. The field exists so the fixture
  exercises the binder skipping unexported fields. Reference it with an
  assertion that it is never set, which both documents the intent and
  satisfies the linter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mromaszewicz
mromaszewicz merged commit 170896a into oapi-codegen:main Oct 23, 2026
12 checks passed
@mromaszewicz
mromaszewicz deleted the feat/bind-json-body branch October 23, 2026 06:20
@mromaszewicz mromaszewicz added the enhancement New feature or request label Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant