Skip to content

openapi3gen: Add an option to customize generated field names (properties) for structs#1204

Merged
fenollp merged 2 commits into
getkin:masterfrom
d1vbyz3r0:add-field-name-generator
Jun 7, 2026
Merged

openapi3gen: Add an option to customize generated field names (properties) for structs#1204
fenollp merged 2 commits into
getkin:masterfrom
d1vbyz3r0:add-field-name-generator

Conversation

@d1vbyz3r0

Copy link
Copy Markdown
Contributor

Description

This PR adds customizable schema property naming.

Currently, openapi3gen derives property names from JSON/YAML tags only, which makes it difficult to integrate with frameworks that use different binding tags (form, query, etc.) while still relying on openapi3gen for schema generation.

The new option allows callers to override the generated property name before it is added to schema.Properties and any customizers are called.

This is particularly useful for generators and framework integrations where request binding semantics differ from JSON serialization semantics, such as Echo's form and xml binding tags.

Usage example

package main

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3gen"
)

type AuthForm struct {
	Login    string `form:"login"`
	Password string `form:"password"`
	Ignore   string `json:"-" form:"-"`
}

func main() {
	schemas := make(openapi3.Schemas)
	g := openapi3gen.NewGenerator(
		openapi3gen.UseAllExportedFields(),
		openapi3gen.CreateFieldNameGenerator(func(field reflect.StructField, defaultName string) string {
			if tag := field.Tag.Get("form"); tag != "" && tag != "-" {
				return tag
			}
			return defaultName
		}),
	)

	ref, _ := g.NewSchemaRefForValue(&AuthForm{}, schemas)
	schema, _ := json.MarshalIndent(ref, "", "  ")
	fmt.Println(string(schema))
	// output:
	// {
	//   "properties": {
	//     "login": {
	//       "type": "string"
	//     },
	//     "password": {
	//       "type": "string"
	//     }
	//   },
	//   "type": "object"
	// }
}

Changes

Existing behaviour remains unchanged when CreateFieldNameGenerator is not provided.

Callback receives defaultName, allowing custom naming rules to preserve names resolved from existing JSON and YAML tags.

Embedded Field Handling

While testing the new feature, I noticed that promoted fields from anonymous embedded structs were generated twice.

Anonymous structs without explicit json tag are already recursively exanded by appendFields:

// See whether this is an embedded field
if f.Anonymous {
jsonTag := f.Tag.Get("json")
if jsonTag == "-" {
continue
}
if jsonTag == "" {
fields = appendFields(fields, index, f.Type)
continue iteration
}

Each promoted field was then additionally processed by the dedicated embedded field branch inside generateWithoutSaving:

if !fieldInfo.HasJSONTag && g.opts.useAllExportedFields {
// Handle anonymous fields/embedded structs
if t.Field(fieldInfo.Index[0]).Anonymous {
ref, err := g.generateSchemaRefFor(parents, fType, fieldName, tag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
ref = g.generateCycleSchemaRef(fType, schema)
} else {
return nil, err
}
}
if ref != nil {
g.SchemaRefs[ref]++
schema.WithPropertyRef(fieldName, ref)
}

Execution subsequently continued through the common field generation path:

ref, err := g.generateSchemaRefFor(parents, fType, fieldName, fieldTag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
ref = g.generateCycleSchemaRef(fType, schema)
} else {
return nil, err
}
}
if ref != nil {
g.SchemaRefs[ref]++
schema.WithPropertyRef(fieldName, ref)
}

This caused the same field schema and SchemaCustomizer callbacks to be processed twice.

Previously, the duplication was hidden because both writes used the same map key. Field name customization exposed this behavior, so I removed the redundant processing branch from generateWithoutSaving and added a regression test.

@fenollp fenollp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks

@fenollp fenollp merged commit e56b2a1 into getkin:master Jun 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants