strict-server: response types silently drop data for allOf-composed unions and properties + additionalProperties

Author: PieterVoorwindenCreated Sep 2, 2026Updated Sep 2, 2026

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

yaml
package: repro
generate:
  strict-server: true
  gorilla-server: true
  models: true
output-options:
  skip-prune: true

Minimal specification

yaml
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:

go
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:

go
type GetThing200JSONResponse Thing   // no MarshalJSON
type GetBag200JSONResponse Bag       // no MarshalJSON

The generated visitor encodes the response value directly:

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

Source: oapi-codegen/oapi-codegen