@@ -21,7 +21,7 @@ namespace Microsoft.OpenApi
2121 /// </summary>
2222 public class OpenApiSchema : IOpenApiExtensible , IOpenApiSchema , IOpenApiSchemaMissingProperties , IOpenApiSchemaWithUnevaluatedProperties , IMetadataContainer
2323 {
24- private static readonly IEnumerable < JsonNode > s_singleNullElementList = [ JsonNullSentinel . JsonNull ] ;
24+ private static readonly IEnumerable < JsonNode > s_singleNullElementList = [ JsonNullSentinel . JsonNull ] ;
2525
2626 /// <inheritdoc />
2727 public string ? Title { get ; set ; }
@@ -549,7 +549,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
549549 }
550550
551551 // type
552- var serializedTypeProperty = TrySerializeTypeProperty ( writer , version ) ;
552+ var serializedTypeProperty = TrySerializeTypePropertyForVersion3AndLater ( writer , version , callback ) ;
553553
554554 // allOf
555555 writer . WriteOptionalCollection ( OpenApiConstants . AllOf , AllOf , callback ) ;
@@ -681,7 +681,7 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer, Action<IOpenApiWrit
681681 writer . WriteProperty ( OpenApiConstants . Id , Id ) ;
682682 writer . WriteProperty ( OpenApiConstants . DollarSchema , Schema ? . ToString ( ) ) ;
683683 writer . WriteProperty ( OpenApiConstants . Comment , Comment ) ;
684-
684+
685685 if ( WasConstExplicitlySet )
686686 {
687687 writer . WriteRequiredProperty ( OpenApiConstants . Const , Const ) ;
@@ -692,7 +692,7 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer, Action<IOpenApiWrit
692692 writer . WriteProperty ( OpenApiConstants . Anchor , Anchor ) ;
693693 writer . WriteProperty ( OpenApiConstants . DynamicRef , DynamicRef ) ;
694694 writer . WriteProperty ( OpenApiConstants . DynamicAnchor , DynamicAnchor ) ;
695-
695+
696696 // UnevaluatedProperties: similar to AdditionalProperties, serialize as schema if present, else as boolean.
697697 // Only emit when the type could include objects.
698698 // Skip when type is explicitly set to a non-object type (array, string, number, integer, boolean, null).
@@ -833,7 +833,7 @@ private void SerializeAsV2(
833833 writer . WriteStartObject ( ) ;
834834
835835 // type
836- TrySerializeTypeProperty ( writer , OpenApiSpecVersion . OpenApi2_0 ) ;
836+ TrySerializeTypePropertyForVersion2 ( writer ) ;
837837
838838 // description
839839 writer . WriteProperty ( OpenApiConstants . Description , Description ) ;
@@ -915,7 +915,7 @@ private void SerializeAsV2(
915915 // oneOf (Not Supported in V2) - Write the first schema only as an allOf.
916916 writer . WriteOptionalCollection ( OpenApiConstants . AllOf , OneOf ? . Take ( 1 ) , ( w , s ) => s . SerializeAsV2 ( w ) ) ;
917917 }
918- #pragma warning restore CS0618
918+ #pragma warning restore CS0618
919919 }
920920
921921 // properties
@@ -996,32 +996,103 @@ private void SerializeAsV2(
996996 writer . WriteEndObject ( ) ;
997997 }
998998
999- private bool TrySerializeTypeProperty ( IOpenApiWriter writer , OpenApiSpecVersion version , JsonSchemaType ? inferredType = null )
999+ private bool TrySerializeTypePropertyForVersion2 ( IOpenApiWriter writer )
10001000 {
1001- // Use original type or inferred type when the explicit type is not set
1002- var typeToUse = Type ?? inferredType ;
1001+ if ( Type is not { } type )
1002+ {
1003+ return false ;
1004+ }
1005+
1006+ var typeWithoutNull = type & ~ JsonSchemaType . Null ;
1007+ if ( typeWithoutNull != 0 && ! HasMultipleTypes ( typeWithoutNull ) )
1008+ {
1009+ writer . WriteProperty ( OpenApiConstants . Type , typeWithoutNull . ToFirstIdentifier ( ) ) ;
1010+ return true ;
1011+ }
1012+
1013+ return false ;
1014+ }
10031015
1004- if ( typeToUse is null )
1016+ /// <summary>
1017+ /// Tries to serialize the "type" property for OpenAPI v3 and later versions.
1018+ /// </summary>
1019+ /// <returns>
1020+ /// true if the Type was serializable using "type" property, and false if
1021+ /// it serialized using anyOf/oneOf or if it couldn't be serialized at all.
1022+ /// </returns>
1023+ private bool TrySerializeTypePropertyForVersion3AndLater ( IOpenApiWriter writer , OpenApiSpecVersion version , Action < IOpenApiWriter , IOpenApiSerializable > callback )
1024+ {
1025+ if ( Type is not { } type )
10051026 {
10061027 return false ;
10071028 }
10081029
1009- switch ( version )
1030+ if ( version == OpenApiSpecVersion . OpenApi3_0 )
10101031 {
1011- case OpenApiSpecVersion . OpenApi2_0 or OpenApiSpecVersion . OpenApi3_0 :
1012- var typeWithoutNull = typeToUse . Value & ~ JsonSchemaType . Null ;
1013- if ( typeWithoutNull != 0 && ! HasMultipleTypes ( typeWithoutNull ) )
1032+ if ( type == JsonSchemaType . Null )
1033+ {
1034+ return false ;
1035+ }
1036+
1037+ var typeWithoutNull = type & ~ JsonSchemaType . Null ;
1038+ var arrayWithoutNull = ( from JsonSchemaType flag in jsonSchemaTypeValues
1039+ where typeWithoutNull . HasFlag ( flag )
1040+ select flag ) . ToArray ( ) ;
1041+ if ( arrayWithoutNull . Length > 1 )
1042+ {
1043+ // If the schema doesn't already have anyOf/oneOf, we can write multiple types as such.
1044+ var canWriteAsAnyOf = AnyOf is not { Count : > 0 } ;
1045+ var canWriteAsOneOf = OneOf is not { Count : > 0 } ;
1046+ if ( canWriteAsAnyOf )
1047+ {
1048+ writer . WriteOptionalCollection ( OpenApiConstants . AnyOf , ConstructChildSchemasForTypes ( arrayWithoutNull ) , callback ) ;
1049+ return false ;
1050+ }
1051+ else if ( canWriteAsOneOf )
10141052 {
1015- writer . WriteProperty ( OpenApiConstants . Type , typeWithoutNull . ToFirstIdentifier ( ) ) ;
1016- return true ;
1053+ writer . WriteOptionalCollection ( OpenApiConstants . OneOf , ConstructChildSchemasForTypes ( arrayWithoutNull ) , callback ) ;
1054+ return false ;
10171055 }
1018- break ;
1019- default :
1020- WriteUnifiedSchemaType ( typeToUse . Value , writer ) ;
1056+ }
1057+ else
1058+ {
1059+ writer . WriteProperty ( OpenApiConstants . Type , arrayWithoutNull [ 0 ] . ToSingleIdentifier ( ) ) ;
10211060 return true ;
1061+ }
1062+ }
1063+ else
1064+ {
1065+ var array = ( from JsonSchemaType flag in jsonSchemaTypeValues
1066+ where type . HasFlag ( flag )
1067+ select flag ) . ToArray ( ) ;
1068+
1069+ if ( array . Length > 1 )
1070+ {
1071+ writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) => w . WriteValue ( s . ToSingleIdentifier ( ) ) ) ;
1072+ }
1073+ else
1074+ {
1075+ writer . WriteProperty ( OpenApiConstants . Type , array [ 0 ] . ToSingleIdentifier ( ) ) ;
1076+ }
1077+
1078+ return true ;
10221079 }
10231080
10241081 return false ;
1082+
1083+ static OpenApiSchema [ ] ConstructChildSchemasForTypes ( JsonSchemaType [ ] types )
1084+ {
1085+ var schemas = new OpenApiSchema [ types . Length ] ;
1086+ for ( int i = 0 ; i < types . Length ; i ++ )
1087+ {
1088+ schemas [ i ] = new OpenApiSchema ( )
1089+ {
1090+ Type = types [ i ]
1091+ } ;
1092+ }
1093+
1094+ return schemas ;
1095+ }
10251096 }
10261097
10271098 private static bool IsPowerOfTwo ( int x )
@@ -1035,27 +1106,6 @@ private static bool HasMultipleTypes(JsonSchemaType schemaType)
10351106 return ! IsPowerOfTwo ( schemaTypeNumeric ) ;
10361107 }
10371108
1038- private static void WriteUnifiedSchemaType ( JsonSchemaType type , IOpenApiWriter writer )
1039- {
1040- var array = ( from JsonSchemaType flag in jsonSchemaTypeValues
1041- where type . HasFlag ( flag )
1042- select flag . ToFirstIdentifier ( ) ) . ToArray ( ) ;
1043- if ( array . Length > 1 )
1044- {
1045- writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) =>
1046- {
1047- if ( ! string . IsNullOrEmpty ( s ) && s is not null )
1048- {
1049- w . WriteValue ( s ) ;
1050- }
1051- } ) ;
1052- }
1053- else
1054- {
1055- writer . WriteProperty ( OpenApiConstants . Type , array [ 0 ] ) ;
1056- }
1057- }
1058-
10591109 private void SerializeNullable ( IOpenApiWriter writer , OpenApiSpecVersion version )
10601110 {
10611111 if ( HasNullType )
0 commit comments