feat: Handle oneOf/anyOf with inline object variants - #150
Open
nikcio wants to merge 11 commits into
Open
Conversation
Previously, oneOf/anyOf unions with inline (non-$ref$) object variants degraded to 'object', losing type information. Now, inline object variants are hoisted to named records, and the union is emitted as an abstract record with [JsonDerivedType] attributes referencing both $ref$ variants and hoisted inline objects. - Extend IsInlineRefUnion to IsInlineUnion: accepts inline objects alongside $ref$ variants - In TryHoistPropertySchema, hoist inline object variants before hoisting the union itself - In DiscoverInlineObjects, scan oneOf/anyOf variants for component-level union schemas - In EmitSimpleUnion, resolve inline object variants to their hoisted type names for [JsonDerivedType] attributes - Add TypeResolver.GetInlineObjectTypeName for looking up hoisted names - Add 4 tests: mixed ref+inline, all-inline, component-level, compilation
…ples - Add escalation oneOf (mixed ref + inline object) to showcase-openapi.yaml - Document the new case in the examples README - Regenerate all example outputs from merged main
- Extend EmitDiscriminatedUnion to emit [JsonDerivedType] for hoisted inline object variants not covered by the discriminator mapping - Add doc comment noting synthetic discriminator for inline variants - Fix xUnit1051 warnings: use TestContext.Current.CancellationToken - Add tests: anyOf with inline, nested inline objects, same-shaped inline variants, discriminated oneOf with inline object - Regenerate all example outputs
…iminated unions
Inline object variants in discriminated unions now use the wire
discriminator value extracted from the variant's discriminator property
(e.g. "dog" from petType: { enum: ["dog"] }) instead of the synthesized
type name. Union variants now inherit from the discriminated union base
type and the discriminator property is skipped on derived types so that
System.TextJson polymorphic deserialization works correctly.
Also fixes orphan hoisted types when a schema has both direct properties
and oneOf/anyOf.
…ission - Extract discriminator value from discProp.Const (OpenAPI 3.1) in TryGetDiscriminatorValue, falling back to discProp.Enum (OpenAPI 3.0) - Skip discriminator properties of union variants in ResolveInlineEnums to avoid emitting orphaned inline enum types - Merge duplicate <remarks> doc comment blocks in EmitSimpleUnion
…riants, and sequential variant naming - Guard against inline variant discriminator value overwriting explicit mapping entries in EmitDiscriminatedUnion - Populate _unionVariantBaseTypes for property-level discriminated unions so both and inline variants inherit from the union base type - Filter null-type variants from anyOf before numbering to produce sequential Variant1, Variant2 names without gaps - Add 4 tests covering all three fixes including a round-trip test - Regenerate examples
… redundant condition - Filter null-type variants in both oneOf and anyOf hoisting paths (DiscoverInlineObjects and TryHoistPropertySchema) so variant numbering stays sequential, consistent with IsInlineUnion. - Remove redundant inner in EmitSimpleUnion that was always true due to the outer guard. - Add oneOf equivalent of the anyOf null-variant sequential naming test.
…ulti-union ref variants Non-discriminated unions with inline object variants now resolve to object instead of hoisting abstract records that System.Text.Json cannot deserialize without a discriminator. This restores the pre-branch behavior for these properties and eliminates ~10% code bloat in the github-api output. Discriminated unions with a $ref variant assigned to a different union base are now skipped in EmitDiscriminatedUnion, preventing invalid [JsonDerivedType] attributes that would cause runtime polymorphic configuration errors (C# single inheritance).
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.
Changes
Previously,
oneOf/anyOfunions with inline (non-ref) object variants degraded toobject, losing type information. Now, inline object variants are hoisted to named records, and the union is emitted as an abstract record with[JsonDerivedType]attributes referencing both ref variants and hoisted inline objects.Before
Property typed as
object?— no type safety, no JSON polymorphism.After
Property typed as
MyRecordValue?— an abstract record with:[JsonDerivedType(typeof(A), "A")][JsonDerivedType(typeof(MyRecordValueVariant2), "MyRecordValueVariant2")]The inline object is hoisted to
MyRecordValueVariant2record.Implementation
IsInlineRefUniontoIsInlineUnion: accepts inline objects alongside ref variantsTryHoistPropertySchema, hoist inline object variants before hoisting the union itselfDiscoverInlineObjects, scanoneOf/anyOfvariants for component-level union schemasEmitSimpleUnion, resolve inline object variants to their hoisted type namesTypeResolver.GetInlineObjectTypeNamefor looking up hoisted namesLimitation
Unions with primitive variants (e.g.
oneOf: [{type: string}, {type: integer}]) still degrade toobjectsince primitives can't be hoisted to records. This could be addressed in a future PR with a customJsonConverter.Testing
All 264 tests pass (260 existing + 4 new). New tests include compilation with
TreatWarningsAsErrors.