Summary
Between v2.5.1 and v2.6.0, pkg/codegen/schema.go dropped a !p.Nullable guard from the omitempty calculation for generated struct fields. As a result, any property with nullable: true in the OpenAPI schema now gets ,omitempty added to its Go JSON tag, where prior versions did not add it. This is a wire-format ("shape") change with no corresponding change to the input spec — the same spec generates code that serializes differently across this version boundary.
There are two distinct manifestations of this:
Case 1: required + nullable fields
A property that is both required: true and nullable: true in a JSON Schema / OpenAPI schema is meant to always appear in the serialized object — its value may be null, but the key is expected to always be present.
Minimal repro:
components:
schemas:
Widget:
type: object
required:
- token
properties:
token:
type: string
nullable: true
readOnly: true
oapi-codegen v2.5.1:
Token *string `json:"token"`
oapi-codegen v2.8.0 (current):
Token *string `json:"token,omitempty"`
Case 2: optional + nullable fields
For a property that's nullable: true but not required, prior versions did not add omitempty unless the nullable-type output option was enabled — this preserved the distinction between "explicitly cleared" (null) and "field absent" (undefined/missing key), which is a meaningful and common REST/JSON convention (e.g. PATCH semantics).
Minimal repro:
components:
schemas:
Widget:
type: object
properties:
note:
type: string
nullable: true
v2.5.1:
Note *string `json:"note"`
Always present in output, null when unset.
v2.8.0:
Note *string `json:"note,omitempty"`
Omitted when unset.
Root cause
Directly diff on pkg/codegen/schema.go across installed module versions shows:
v2.5.1 (last unaffected): omitEmpty := !p.Nullable && shouldOmitEmpty
v2.6.0 (first changed): omitEmpty := shouldOmitEmpty
(confirmed unchanged through v2.7.0, v2.7.1, v2.7.2, v2.8.0 — the latest release as of this report)
Versions
- github.com/oapi-codegen/oapi-codegen/v2: behavior changed starting at v2.6.0, present through v2.8.0 (latest at time of writing); confirmed as v2.5.1's behavior in v2.4.1, v2.5.0, v2.5.1.
- Reproduced with github.com/getkin/kin-openapi v0.145.0, but the change is entirely internal to oapi-codegen's own codegen logic — it reproduces identically regardless of kin-openapi version, using the exact same input spec across the version boundary above.
- Go 1.26.0 (shouldn't matter, but noting for completeness).
- Codegen config: generate: {models: true} (also reproduces with strict-server/chi-server enabled).
Workaround found
Adding x-omitempty: false in the spec seem to mitigate, but it is undesirable to change the specs to preserve the older behavior
Summary
Between v2.5.1 and v2.6.0, pkg/codegen/schema.go dropped a !p.Nullable guard from the omitempty calculation for generated struct fields. As a result, any property with nullable: true in the OpenAPI schema now gets ,omitempty added to its Go JSON tag, where prior versions did not add it. This is a wire-format ("shape") change with no corresponding change to the input spec — the same spec generates code that serializes differently across this version boundary.
There are two distinct manifestations of this:
Case 1: required + nullable fields
A property that is both required: true and nullable: true in a JSON Schema / OpenAPI schema is meant to always appear in the serialized object — its value may be null, but the key is expected to always be present.
Minimal repro:
oapi-codegen v2.5.1:
oapi-codegen v2.8.0 (current):
Case 2: optional + nullable fields
For a property that's nullable: true but not required, prior versions did not add omitempty unless the nullable-type output option was enabled — this preserved the distinction between "explicitly cleared" (null) and "field absent" (undefined/missing key), which is a meaningful and common REST/JSON convention (e.g. PATCH semantics).
Minimal repro:
v2.5.1:
Always present in output, null when unset.
v2.8.0:
Omitted when unset.
Root cause
Directly diff on pkg/codegen/schema.go across installed module versions shows:
(confirmed unchanged through v2.7.0, v2.7.1, v2.7.2, v2.8.0 — the latest release as of this report)
Versions
Workaround found
Adding x-omitempty: false in the spec seem to mitigate, but it is undesirable to change the specs to preserve the older behavior