@@ -438,23 +438,39 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
438438
439439 // enum
440440 var enumValue = Enum is not { Count : > 0 }
441- && ! string . IsNullOrEmpty ( Const )
441+ && ! string . IsNullOrEmpty ( Const )
442442 && version < OpenApiSpecVersion . OpenApi3_1
443443 ? new List < JsonNode > { JsonValue . Create ( Const ) ! }
444444 : Enum ;
445445 writer . WriteOptionalCollection ( OpenApiConstants . Enum , enumValue , ( nodeWriter , s ) => nodeWriter . WriteAny ( s ) ) ;
446446
447+ // Handle oneOf/anyOf with null type for v3.0 downcast
448+ IList < IOpenApiSchema > ? effectiveOneOf = OneOf ;
449+ IList < IOpenApiSchema > ? effectiveAnyOf = AnyOf ;
450+ bool hasNullInComposition = false ;
451+ JsonSchemaType ? inferredType = null ;
452+
453+ if ( version == OpenApiSpecVersion . OpenApi3_0 )
454+ {
455+ ( effectiveOneOf , var inferredOneOf , var nullInOneOf ) = ProcessCompositionForNull ( OneOf ) ;
456+ hasNullInComposition |= nullInOneOf ;
457+ inferredType = inferredOneOf ?? inferredType ;
458+ ( effectiveAnyOf , var inferredAnyOf , var nullInAnyOf ) = ProcessCompositionForNull ( AnyOf ) ;
459+ hasNullInComposition |= nullInAnyOf ;
460+ inferredType = inferredAnyOf ?? inferredType ;
461+ }
462+
447463 // type
448- SerializeTypeProperty ( writer , version ) ;
464+ SerializeTypeProperty ( writer , version , inferredType ) ;
449465
450466 // allOf
451467 writer . WriteOptionalCollection ( OpenApiConstants . AllOf , AllOf , callback ) ;
452468
453469 // anyOf
454- writer . WriteOptionalCollection ( OpenApiConstants . AnyOf , AnyOf , callback ) ;
470+ writer . WriteOptionalCollection ( OpenApiConstants . AnyOf , effectiveAnyOf , callback ) ;
455471
456472 // oneOf
457- writer . WriteOptionalCollection ( OpenApiConstants . OneOf , OneOf , callback ) ;
473+ writer . WriteOptionalCollection ( OpenApiConstants . OneOf , effectiveOneOf , callback ) ;
458474
459475 // not
460476 writer . WriteOptionalObject ( OpenApiConstants . Not , Not , callback ) ;
@@ -493,7 +509,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
493509 // nullable
494510 if ( version == OpenApiSpecVersion . OpenApi3_0 )
495511 {
496- SerializeNullable ( writer , version ) ;
512+ SerializeNullable ( writer , version , hasNullInComposition ) ;
497513 }
498514
499515 // discriminator
@@ -766,14 +782,17 @@ private void SerializeAsV2(
766782 writer . WriteEndObject ( ) ;
767783 }
768784
769- private void SerializeTypeProperty ( IOpenApiWriter writer , OpenApiSpecVersion version )
785+ private void SerializeTypeProperty ( IOpenApiWriter writer , OpenApiSpecVersion version , JsonSchemaType ? inferredType = null )
770786 {
771- if ( Type is null )
787+ // Use original type or inferred type when the explicit type is not set
788+ var typeToUse = Type ?? inferredType ;
789+
790+ if ( typeToUse is null )
772791 {
773792 return ;
774793 }
775794
776- var unifiedType = IsNullable ? Type . Value | JsonSchemaType . Null : Type . Value ;
795+ var unifiedType = IsNullable ? typeToUse . Value | JsonSchemaType . Null : typeToUse . Value ;
777796 var typeWithoutNull = unifiedType & ~ JsonSchemaType . Null ;
778797
779798 switch ( version )
@@ -804,8 +823,8 @@ private static bool HasMultipleTypes(JsonSchemaType schemaType)
804823 private static void WriteUnifiedSchemaType ( JsonSchemaType type , IOpenApiWriter writer )
805824 {
806825 var array = ( from JsonSchemaType flag in jsonSchemaTypeValues
807- where type . HasFlag ( flag )
808- select flag . ToFirstIdentifier ( ) ) . ToArray ( ) ;
826+ where type . HasFlag ( flag )
827+ select flag . ToFirstIdentifier ( ) ) . ToArray ( ) ;
809828 if ( array . Length > 1 )
810829 {
811830 writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) =>
@@ -822,9 +841,9 @@ where type.HasFlag(flag)
822841 }
823842 }
824843
825- private void SerializeNullable ( IOpenApiWriter writer , OpenApiSpecVersion version )
844+ private void SerializeNullable ( IOpenApiWriter writer , OpenApiSpecVersion version , bool hasNullInComposition = false )
826845 {
827- if ( IsNullable )
846+ if ( IsNullable || hasNullInComposition )
828847 {
829848 switch ( version )
830849 {
@@ -838,6 +857,41 @@ private void SerializeNullable(IOpenApiWriter writer, OpenApiSpecVersion version
838857 }
839858 }
840859
860+ /// <summary>
861+ /// Processes a composition (oneOf or anyOf) for null types, filtering out null schemas and inferring common type.
862+ /// </summary>
863+ /// <param name="composition">The list of schemas in the composition.</param>
864+ /// <returns>A tuple with the effective list, inferred type, and whether null is present in composition.</returns>
865+ private static ( IList < IOpenApiSchema > ? effective , JsonSchemaType ? inferredType , bool hasNullInComposition )
866+ ProcessCompositionForNull ( IList < IOpenApiSchema > ? composition )
867+ {
868+ if ( composition is null || ! composition . Any ( static s => s . Type is JsonSchemaType . Null ) )
869+ {
870+ // Nothing to patch
871+ return ( composition , null , false ) ;
872+ }
873+
874+ var nonNullSchemas = composition
875+ . Where ( static s => s . Type is null or not JsonSchemaType . Null )
876+ . ToList ( ) ;
877+
878+ if ( nonNullSchemas . Count > 0 )
879+ {
880+ JsonSchemaType commonType = 0 ;
881+
882+ foreach ( var schema in nonNullSchemas )
883+ {
884+ commonType |= schema . Type . GetValueOrDefault ( ) & ~ JsonSchemaType . Null ;
885+ }
886+
887+ return ( nonNullSchemas , commonType , true ) ;
888+ }
889+ else
890+ {
891+ return ( null , null , true ) ;
892+ }
893+ }
894+
841895#if NET5_0_OR_GREATER
842896 private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues < JsonSchemaType > ( ) ;
843897#else
0 commit comments