PR #2332 fixed strict-server response types losing their model's MarshalJSON, closing #970 and #1665. Two shapes are still affected: the response is emitted as a defined type with no delegation, and encoding/json quietly serialises an incomplete body. No error, no compile failure — just a 200 with fields missing.
Environment
oapi-codegen v2.8.0
oapi-codegen/runtime v1.6.0
- Go 1.26.4 (darwin/arm64)
Configuration
package: repro
generate:
strict-server: true
gorilla-server: true
models: true
output-options:
skip-prune: true
Minimal specification
openapi: "3.0.0"
info:
title: allOf-composed union repro
version: "1.0"
paths:
/thing:
get:
operationId: getThing
responses:
"200":
description: ok
content:
application/json:
schema: { $ref: "#/components/schemas/Thing" }
/bag:
get:
operationId: getBag
responses:
"200":
description: ok
content:
application/json:
schema: { $ref: "#/components/schemas/Bag" }
components:
schemas:
Base:
type: object
required: [id]
properties:
id: { type: string }
# case 1: union nested inside an allOf member
Thing:
allOf:
- $ref: "#/components/schemas/Base"
- oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
Cat:
type: object
properties: { meow: { type: string } }
Dog:
type: object
properties: { woof: { type: string } }
# case 2: fixed properties alongside additionalProperties
Bag:
type: object
required: [kind]
properties:
kind: { type: string }
additionalProperties: true
Current behaviour
The models are generated correctly. Both get a custom marshaller:
type Thing struct {
Id string `json:"id"`
union json.RawMessage
}
func (t Thing) MarshalJSON() ([]byte, error) { ... }
type Bag struct {
Kind string `json:"kind"`
AdditionalProperties map[string]interface{} `json:"-"`
}
func (a Bag) MarshalJSON() ([]byte, error) { ... }
The response types do not, so nothing delegates to those marshallers:
type GetThing200JSONResponse Thing // no MarshalJSON
type GetBag200JSONResponse Bag // no MarshalJSON
The generated visitor encodes the response value directly:
func (response GetThing200JSONResponse) VisitGetThingResponse(w http.ResponseWriter) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(response); err != nil {
return err
}
...
}
Because GetThing200JSONResponse is a defined type it does not inherit Thing's method, so encoding/json falls back to struct-field encoding and drops the unexported union field. Same for Bag, whose AdditionalProperties map is tagged json:"-".
Serving each response through its generated visitor:
| Response value |
Actual (v2.8.0) |
Expected |
Thing{Id: "abc"} + FromCat(Cat{Meow: "purr"}) |
{"id":"abc"} |
{"id":"abc","meow":"purr"} |
Bag{Kind: "demo", AdditionalProperties: {"extra": 42}} |
{"kind":"demo"} |
{"extra":42,"kind":"demo"} |
Expected behaviour
The response type should delegate to the model's marshaller, exactly as #2332 already does for a response whose schema is directly a oneOf/anyOf.
PR #2332 fixed strict-server response types losing their model's
MarshalJSON, closing #970 and #1665. Two shapes are still affected: the response is emitted as a defined type with no delegation, andencoding/jsonquietly serialises an incomplete body. No error, no compile failure — just a 200 with fields missing.Environment
oapi-codegenv2.8.0oapi-codegen/runtimev1.6.0Configuration
Minimal specification
Current behaviour
The models are generated correctly. Both get a custom marshaller:
The response types do not, so nothing delegates to those marshallers:
The generated visitor encodes the response value directly:
Because
GetThing200JSONResponseis a defined type it does not inheritThing's method, soencoding/jsonfalls back to struct-field encoding and drops the unexportedunionfield. Same forBag, whoseAdditionalPropertiesmap is taggedjson:"-".Serving each response through its generated visitor:
Thing{Id: "abc"}+FromCat(Cat{Meow: "purr"}){"id":"abc"}{"id":"abc","meow":"purr"}Bag{Kind: "demo", AdditionalProperties: {"extra": 42}}{"kind":"demo"}{"extra":42,"kind":"demo"}Expected behaviour
The response type should delegate to the model's marshaller, exactly as #2332 already does for a response whose schema is directly a
oneOf/anyOf.