Skip to content

[BUG][Go] No json tags emitted when a schema has both properties and a schema-level oneOf, silently dropping snake_case fields on decode #24916

Description

@henry-hs-choi

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master? — tested with the latest release, v7.25.0
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
Description

When a schema declares properties and a schema-level oneOf, the Go generator emits the model struct with no json tags at all. Go's decoder then falls back to matching field names case-insensitively, and that fallback does not ignore underscores — so every property whose wire name contains one is silently dropped on decode, while single-word properties decode fine.

This is worse than a cosmetic tag issue:

  • MarshalJSON is generated from a hand-built ToMap() that keeps the correct snake_case keys, so serialization stays healthy. Only deserialization loses data, and only for some fields.
  • The generated UnmarshalJSON deletes the known keys from AdditionalProperties, so the value is not recoverable there either.
  • Tests that construct the model in Go and assert on its fields never touch the tags, so they stay green while decoding is broken. The failure surfaces only against a real payload.

The oneOf involved need not define variants. In the repro it is a pure co-occurrence constraint — "either first_value or second_value, not both" — which adds no shape and which the Go generator cannot express in the type system either way. It is valid OpenAPI, and it is the natural way to describe a schema that has gained a second mutually exclusive encoding, yet its mere presence costs the schema every tag.

openapi-generator version

7.25.0. Also reproduced on 7.18.0, so this is not a recent regression.

OpenAPI declaration file content
openapi: 3.0.3
info:
  title: Tag Loss Repro
  version: 1.0.0
paths:
  /thing:
    get:
      operationId: getThing
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Thing'
components:
  schemas:
    Thing:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
        first_value:
          type: array
          items:
            type: number
        second_value:
          type: array
          items:
            type: number
      oneOf:
        - required: [first_value]
          not:
            required: [second_value]
        - required: [second_value]
          not:
            required: [first_value]
Generation details
docker run --rm -v "$PWD:/local" openapitools/openapi-generator-cli:v7.25.0 generate \
  -i /local/spec.yaml -g go -o /local/out \
  --additional-properties="withGoMod=false,packageName=client"

Actual

type Thing struct {
	Kind string
	FirstValue []float32
	SecondValue []float32
}

Expected — and exactly what the generator produces from the same spec with the oneOf block removed, which isolates the trigger:

type Thing struct {
	Kind string `json:"kind"`
	FirstValue []float32 `json:"first_value,omitempty"`
	SecondValue []float32 `json:"second_value,omitempty"`
}

With the oneOf present, json.Unmarshal of {"kind":"example","first_value":[1,2],"second_value":[3,4]} leaves both slices empty while Kind is populated.

Related

#21461 reports missing omitempty on the members of a pure oneOf union. Possibly the same code path classifying any schema carrying oneOf as a union model, but the symptom and blast radius differ: that one loses an omitempty on a wrapper, this one loses every tag on an ordinary model and drops data.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions