feat(gen): add type-based oneOf/anyOf discrimination#1584
Merged
ernado merged 1 commit intoogen-go:mainfrom Nov 27, 2025
Merged
feat(gen): add type-based oneOf/anyOf discrimination#1584ernado merged 1 commit intoogen-go:mainfrom
ernado merged 1 commit intoogen-go:mainfrom
Conversation
Member
|
Please check why github example is now not generated. Is strange. |
Contributor
Author
|
@ernado yes ... that is strange. i'll get into it. |
b890848 to
53549e4
Compare
53549e4 to
4a97e67
Compare
Contributor
Author
|
I have some other changes that snuck in here that I'm working through. |
f760edd to
f2d3d9f
Compare
f2d3d9f to
4b3acf1
Compare
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 26, 2025
Fixes two critical issues preventing broken code generation for type-based
discrimination:
## Issue 1: Enum types not mapped to jxType
**Problem:**
Enum type IDs like 'enum_ChecksCreateReqSum0Status' fell through to the
default case in jxTypeForFieldType(), returning empty string. This caused
empty FieldType values in template data, resulting in empty switch statements
in generated decoders.
**Symptoms:**
- ChecksCreateReq decoder had empty switch with only default case
- Runtime error: "unable to detect sum type variant"
**Fix:**
Added enum handling in jxTypeForFieldType() (gen/schema_gen_sum.go:62-64):
case strings.HasPrefix(typeID, "enum_"):
return "jx.String"
Enums serialize as strings in JSON, so they map to jx.String for runtime
type checking.
## Issue 2: Array element types indistinguishable at runtime
**Problem:**
Arrays with different element types (e.g., []ValidationErrorErrorsItem vs
[]string) both map to jx.Array. The runtime can't distinguish them using
d.Next() alone, which only checks JSON type, not array contents.
Template deduplication based on FieldType would leave only one variant,
causing incorrect discrimination.
**Symptoms:**
- OrgsUpdateUnprocessableEntity decoder generated with broken logic
- ProjectsCreateCardUnprocessableEntity decoder generated with broken logic
- Tests failed with "unable to detect sum type variant"
**Fix:**
Added validation in oneOf() to detect when all variants for a field have
the same jxType after mapping (gen/schema_gen_sum.go:864-906). Returns
clear error instead of generating broken code:
"field 'errors' cannot discriminate variants (requires unsupported
discrimination): [ValidationError: array[object] ValidationErrorSimple:
array[string]]"
This allows schemas to be properly skipped with ignore_not_implemented
configuration rather than generating broken decoders.
## Test Updates
Updated gen_test.go to include "type-based discrimination with same jxType"
in the GitHub API ignore list, as this error now properly catches the
unsupported array discrimination cases.
## Verification
- ✅ All unit tests passing
- ✅ All example tests passing
- ✅ GitHub API: 728 operations generated (12 properly skipped)
- ✅ No broken decoders in generated code
- ✅ Clear error messages for unsupported cases
Related to PR ogen-go#1584
2ccbeb9 to
06a0fee
Compare
Implement type-based discrimination as the 4th discrimination strategy for oneOf/anyOf schemas, enabling automatic variant selection based on JSON type signatures without explicit discriminator fields. Core Implementation: - Field signature tracking using (name, typeID) pairs - Runtime type checking via O(1) jx.Decoder.Next() operation - IR metadata for FieldType and Nullable detection - jxTypeForFieldType() mapping IR types to jx JSON types Robustness Enhancements: - Enum type handling (maps enum_* to jx.String) - Nullable type detection (generic NilT and pointer-based) - Array element discrimination validation - Value-based discriminator validation with clear error messages Sum Type Parameter Support: - Restore sum type URI encode/decode capabilities - Handle empty schemas appropriately for requests vs responses - Add parameter handling for sum types Testing: - 8 new test specifications covering type discrimination scenarios - Regression tests for sum_type_params and empty_response_body - Examples regeneration showing GitHub API improvement (734/740 ops) Impact: - Fixes ogen-go#1013 (nested sum type discrimination) - Fixes ogen-go#1185 (unique fields incorrectly rejected) - GitHub API: 99.2% operation success (up from 98.4%) - Telegram/GoTD APIs benefit from improved discrimination Files changed: 160+ files, +16,000 lines
06a0fee to
40e0719
Compare
Contributor
Author
|
@ernado a lot of trashing on my end, but I think I got it now. |
ernado
approved these changes
Nov 27, 2025
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 27, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 27, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 27, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
lanej
added a commit
to lanej/ogen
that referenced
this pull request
Nov 30, 2025
Enable automatic discrimination between oneOf variants that have array fields with different element types (e.g., string[] vs integer[] vs boolean[]). This extends the type-based discrimination added in PR ogen-go#1584 to support cases where variants share the same field name with array types but differ in their element types. Implementation: - Add ArrayElementType and ArrayElementTypeID fields to UniqueFieldVariant - Add getArrayElementTypeInfo() to extract element type from array type IDs - Update validation to allow discrimination when array element types differ - Generate decoder code that peeks into arrays using d.Capture() and d.ArrIter() to check first element type without consuming Supported cases: - Basic primitives: string[] vs integer[] vs boolean[] - Object vs primitive: object[] vs string[] - Mixed: array type combined with unique field discrimination Limitations (future work): - Nested arrays (array[array[string]] vs array[array[integer]]) - Complex object arrays (User[] vs Product[] with same object type)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements type-based discrimination as the 4th discrimination strategy for
oneOf/anyOfschemas, enabling ogen to automatically distinguish sum type variants that share field names but differ in types (e.g.,{id: string}vs{id: integer}).This completes ogen's discrimination strategy suite:
discriminatorfield)anyOfwith different primitive types)Motivation
Fixes two long-standing issues:
Previously, ogen would fail to generate code for schemas like:
Now these schemas work seamlessly with automatic type-based discrimination.
Implementation
Core Mechanism
Field Signature Tracking: Changed from tracking just field names to tracking
(name, typeID)pairs:{id: string}→ signature{name: "id", typeID: "string"}{id: integer}→ signature{name: "id", typeID: "int"}{id: array[string]}→ signature{name: "id", typeID: "array[string]"}Runtime Type Checking: Uses
jx.Decoder.Next()(O(1) operation) to peek at JSON type:Type Mapping:
jxTypeForFieldType()maps IR type IDs to jx JSON types:"string","enum_*"→jx.String"int","int32","int64"→jx.Number"array[*]"→jx.Array"object", custom types →jx.ObjectNilString,OptNilInt) detected via naming patternsRobustness Features
✅ Enum Handling: Enum types correctly map to
jx.Stringfor discrimination✅ Nullable Types: Both generic (
NilT,OptNilT) and pointer-based nullables detected✅ Array Validation: Rejects unsupported array element type discrimination with clear errors
✅ Value-Based Validation: Rejects same field+type with different enum values (out of scope)
✅ Nested Support: Works correctly with nested oneOf structures
Additional Work
Also restores sum type parameter support that exists in upstream:
any, rejected in requests)sum_type_params.yamlandempty_response_body.jsonTesting
New Test Specifications (8)
type_discriminated_fields.json— Basic type-based discriminationnullable_type_discrimination.json— Nullable field handlingoptional_type_discrimination.json— Optional field handlingarray_object_type_discrimination.json— Array vs object discriminationhybrid_discrimination.json— 3+ variants with mixed strategiesmixed_discrimination.json— Field name + type discrimination combinednested_type_discrimination.json— Nested oneOf structuresfield_name_discrimination.json— Baseline regression testIntegration Tests (5 new test suites)
Complete client/server implementations generated for all new test specifications, validating end-to-end functionality.
Real-World Validation
GitHub API: 734/740 operations (99.2% success)
Other APIs: Telegram, GoTD, and K8s examples benefit from improved discrimination
Test Results
All tests passing:
TestGenerate/Positive/*(all 8 new specs)TestGenerate/Examples/api.github.comTestGenerate/Positive/sum_type_paramsTestGenerate/Positive/empty_response_bodyPerformance Impact
Zero Performance Overhead:
jx.Decoder.Next()is O(1) peek operation (no data consumed, no allocation)Breaking Changes
None. Fully backward compatible:
Documentation
Files Changed
gen/schema_gen_sum.go(+408 lines)gen/_template/json/encoders_sum.tmpl(+51 lines)gen/ir/type.go(+18 lines)gen/_template/uri/*.tmpl(+33 lines)_testdata/positive/Known Limitations
These patterns remain unsupported (by design):
❌ Value-based discrimination: Same field name and type, different enum values
{status: "active"}vs{status: "inactive"}❌ Array element type discrimination: Different array element types
array[string]vsarray[integer]Arrayfor both, can't discriminate without inspecting elementsThese are fundamental limitations that would require significantly more complex runtime logic. The current implementation draws a clean line at JSON type-level discrimination, which covers the vast majority of real-world use cases.
Review Notes
For Maintainers:
gen/schema_gen_sum.go(~400 lines)Diff Organization:
gen/*.gofilesgen/_template/directory_testdata/positive/directoryinternal/integration/test_*/directoriesexamples/ex_*/directoriesCloses