Skip to content

fix: resolve Go field name collisions when JSON keys differ only by separator (#2495) - #2550

Open
rifkir23 wants to merge 1 commit into
oapi-codegen:mainfrom
rifkir23:fix/field-name-collision-hyphen-underscore
Open

rifkir23 wants to merge 1 commit into
oapi-codegen:mainfrom
rifkir23:fix/field-name-collision-hyphen-underscore

Conversation

@rifkir23

@rifkir23 rifkir23 commented Sep 3, 2026

Copy link
Copy Markdown

Problem

When two JSON properties in the same object differ only by their separator — e.g. host-fqdn and host_fqdn — both normalize to the same Go field name HostFqdn, producing a redeclared in this block compile error in the generated code. - is not a valid Go identifier character so stripping it is unavoidable, and _ folds to the same PascalCase, so the two collide.

This is reproducible against real specs (the issue references the Tenable Vulnerability Management API, whose info object contains both host-fqdn and host_fqdn).

Fixes #2495

Fix

Resolve Go field-name collisions within a struct in GenFieldsFromProperties. The first property to claim a name keeps it; each later property that would collide gets an incrementing numeric suffix (HostFqdn, HostFqdn2, HostFqdn3, ...), re-checked so a suffixed name cannot itself collide. This matches the direction suggested in the issue ("suffix numbers on colliding fields") and is consistent with the collision-resolution the codebase already does elsewhere (enum constants, type names).

Only the Go identifier changes — the JSON struct tag still uses the original JsonFieldName, so the wire format is unchanged and round-trips exactly as before. Explicit x-go-name overrides continue to work and take precedence.

Tests

Added TestGenFieldsFromProperties_ResolvesGoFieldNameCollisions: two properties (host-fqdn, host_fqdn) now generate HostFqdn and HostFqdn2 with their original JSON tags preserved. Verified the test FAILS without the fix (both fields render as HostFqdn, matching the reported compile error) and PASSES with it. Full pkg/codegen suite passes; gofmt and go vet are clean.

@rifkir23
rifkir23 requested a review from a team as a code owner September 3, 2026 13:21
@greptile-apps

greptile-apps Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds numeric disambiguation for object properties whose JSON names normalize to the same Go field identifier while preserving their original wire tags.

  • Adds a property-level collision-resolution pass in schema generation.
  • Adds a unit test for two separator-differing property names.
  • The resolved names are not propagated to custom serialization helpers, and the resolver does not reserve the synthetic AdditionalProperties field.

Confidence Score: 3/5

This PR should not merge until collision-resolved names are shared with serialization boilerplate and synthetic struct fields are included in collision handling.

Colliding properties can now compile into structs whose generated marshal/unmarshal methods access the wrong field, while an AdditionalProperties property can still produce an uncompilable duplicate declaration.

Files Needing Attention: pkg/codegen/schema.go and the union/additional-properties boilerplate templates

Important Files Changed

Filename Overview
pkg/codegen/schema.go Adds field-name disambiguation, but the resolved identifiers diverge from serialization boilerplate and omit a synthetic-field collision.
pkg/codegen/schema_test.go Covers the basic two-property declaration and JSON-tag case but not additional-properties or union boilerplate paths.
Prompt To Fix All With AI
### Issue 1
pkg/codegen/schema.go:1657-1661
**Boilerplate uses unresolved field names**

When a union or additional-properties object contains properties such as `host-fqdn` and `host_fqdn`, the struct declares the latter as `HostFqdn2`, but generated marshal/unmarshal templates still resolve both accesses as `HostFqdn`, causing the second JSON property to read from or write to the wrong Go field.

### Issue 2
pkg/codegen/schema.go:1779-1784
**Synthetic field collision remains**

When an object allows additional properties and also declares a property named `additionalProperties` or uses `x-go-name: AdditionalProperties`, the resolver does not reserve the synthetic field name, so `GenStructFromSchema` emits two `AdditionalProperties` fields and the generated Go code fails to compile.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix: resolve Go field name collisions wh..." | Re-trigger Greptile

Comment thread pkg/codegen/schema.go Outdated
Comment on lines +1657 to +1661
goFieldNames := resolveFieldNameCollisions(props)
for i, p := range props {
field := ""

goFieldName := p.GoFieldName()
goFieldName := goFieldNames[i]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Boilerplate uses unresolved field names

When a union or additional-properties object contains properties such as host-fqdn and host_fqdn, the struct declares the latter as HostFqdn2, but generated marshal/unmarshal templates still resolve both accesses as HostFqdn, causing the second JSON property to read from or write to the wrong Go field.

Knowledge Base Used: Schema and Go model generation

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/codegen/schema.go
Line: 1657-1661

Comment:
**Boilerplate uses unresolved field names**

When a union or additional-properties object contains properties such as `host-fqdn` and `host_fqdn`, the struct declares the latter as `HostFqdn2`, but generated marshal/unmarshal templates still resolve both accesses as `HostFqdn`, causing the second JSON property to read from or write to the wrong Go field.

**Knowledge Base Used:** [Schema and Go model generation](https://app.greptile.com/oapi-codegen/-/custom-context/knowledge-base/oapi-codegen/oapi-codegen/-/docs/schema-and-model-generation.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread pkg/codegen/schema.go Outdated
Comment on lines +1779 to +1784
seen := make(map[string]struct{}, len(props))
for i, p := range props {
name := p.GoFieldName()
if _, exists := seen[name]; exists {
for n := 2; ; n++ {
candidate := fmt.Sprintf("%s%d", name, n)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Synthetic field collision remains

When an object allows additional properties and also declares a property named additionalProperties or uses x-go-name: AdditionalProperties, the resolver does not reserve the synthetic field name, so GenStructFromSchema emits two AdditionalProperties fields and the generated Go code fails to compile.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/codegen/schema.go
Line: 1779-1784

Comment:
**Synthetic field collision remains**

When an object allows additional properties and also declares a property named `additionalProperties` or uses `x-go-name: AdditionalProperties`, the resolver does not reserve the synthetic field name, so `GenStructFromSchema` emits two `AdditionalProperties` fields and the generated Go code fails to compile.

**Knowledge Base Used:**
- [Schema and Go model generation](https://app.greptile.com/oapi-codegen/-/custom-context/knowledge-base/oapi-codegen/oapi-codegen/-/docs/schema-and-model-generation.md)
- [Name shared path parameter helpers safely](https://app.greptile.com/oapi-codegen/-/custom-context/knowledge-base/oapi-codegen/oapi-codegen/-/reverts/revert_2476-20260716-shared-path-parameter-collision-bd52535.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

…eparator (oapi-codegen#2495)

Move collision resolution from GenFieldsFromProperties to a single-source
resolver (ResolvePropertyGoFieldNameCollisions) that populates an unexported
resolvedGoFieldName on each Property. GoFieldName() now returns the resolved
name, so the struct declaration AND the generated marshal/unmarshal boilerplate
(union, additional-properties templates) all share the same disambiguated
field name. Also reserves the synthetic AdditionalProperties field name so a
real property named 'additionalProperties' does not collide with it.

Fixes oapi-codegen#2495
@rifkir23
rifkir23 force-pushed the fix/field-name-collision-hyphen-underscore branch from ba4b9de to c1eb68a Compare September 3, 2026 15:35
@rifkir23

rifkir23 commented Sep 3, 2026

Copy link
Copy Markdown
Author

thanks for the careful review, both points were valid. i reworked the fix so it is a single source of truth instead of only touching the struct declaration:

  • collision resolution now runs once per schema (ResolvePropertyGoFieldNameCollisions) and stores the disambiguated name on the property. GoFieldName() returns it, so the struct declaration and the generated marshal/unmarshal boilerplate (union and additional-properties templates, which all read GoFieldName) now use the same name. this fixes the "boilerplate reads the wrong field" case.
  • the resolver reserves the synthetic AdditionalProperties field name first, so a real property named additionalProperties (or one using x-go-name: AdditionalProperties) is suffixed instead of producing a duplicate declaration.

added a test for the additional-properties reservation case as well. full pkg/codegen suite and the rest of ./... pass, gofmt and vet clean.

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.

oapi-codegen v2 produces duplicate Go field names when JSON keys differ only by separator (hyphen vs underscore)

2 participants