@@ -104,6 +104,14 @@ public string? ExclusiveMinimum
104104 /// <inheritdoc />
105105 public JsonSchemaType ? Type { get ; set ; }
106106
107+ // x-nullable is filtered out by deserializers, but keep the check here in case it gets added from user code.
108+ private bool IsNullable =>
109+ ( Type . HasValue && Type . Value . HasFlag ( JsonSchemaType . Null ) ) ||
110+ Extensions is not null &&
111+ Extensions . TryGetValue ( OpenApiConstants . NullableExtension , out var nullExtRawValue ) &&
112+ nullExtRawValue is JsonNodeExtension { Node : JsonNode jsonNode } &&
113+ jsonNode . GetValueKind ( ) is JsonValueKind . True ;
114+
107115 /// <inheritdoc />
108116 public string ? Const { get ; set ; }
109117
@@ -437,7 +445,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
437445 writer . WriteOptionalCollection ( OpenApiConstants . Enum , Enum , ( nodeWriter , s ) => nodeWriter . WriteAny ( s ) ) ;
438446
439447 // type
440- SerializeTypeProperty ( Type , writer , version ) ;
448+ SerializeTypeProperty ( writer , version ) ;
441449
442450 // allOf
443451 writer . WriteOptionalCollection ( OpenApiConstants . AllOf , AllOf , callback ) ;
@@ -479,6 +487,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
479487 // default
480488 writer . WriteOptionalObject ( OpenApiConstants . Default , Default , ( w , d ) => w . WriteAny ( d ) ) ;
481489
490+ // nullable
491+ if ( version == OpenApiSpecVersion . OpenApi3_0 )
492+ {
493+ SerializeNullable ( writer , version ) ;
494+ }
495+
482496 // discriminator
483497 writer . WriteOptionalObject ( OpenApiConstants . Discriminator , Discriminator , callback ) ;
484498
@@ -619,7 +633,7 @@ private void SerializeAsV2(
619633 writer . WriteStartObject ( ) ;
620634
621635 // type
622- SerializeTypeProperty ( Type , writer , OpenApiSpecVersion . OpenApi2_0 ) ;
636+ SerializeTypeProperty ( writer , OpenApiSpecVersion . OpenApi2_0 ) ;
623637
624638 // description
625639 writer . WriteProperty ( OpenApiConstants . Description , Description ) ;
@@ -742,66 +756,36 @@ private void SerializeAsV2(
742756 // example
743757 writer . WriteOptionalObject ( OpenApiConstants . Example , Example , ( w , e ) => w . WriteAny ( e ) ) ;
744758
759+ // x-nullable extension
760+ SerializeNullable ( writer , OpenApiSpecVersion . OpenApi2_0 ) ;
761+
745762 // extensions
746763 writer . WriteExtensions ( Extensions , OpenApiSpecVersion . OpenApi2_0 ) ;
747764
748765 writer . WriteEndObject ( ) ;
749766 }
750767
751- private void SerializeTypeProperty ( JsonSchemaType ? type , IOpenApiWriter writer , OpenApiSpecVersion version )
768+ private void SerializeTypeProperty ( IOpenApiWriter writer , OpenApiSpecVersion version )
752769 {
753- // check whether nullable is true for upcasting purposes
754- var isNullable = ( Type . HasValue && Type . Value . HasFlag ( JsonSchemaType . Null ) ) ||
755- Extensions is not null &&
756- Extensions . TryGetValue ( OpenApiConstants . NullableExtension , out var nullExtRawValue ) &&
757- nullExtRawValue is JsonNodeExtension { Node : JsonNode jsonNode } &&
758- jsonNode . GetValueKind ( ) is JsonValueKind . True ;
759- if ( type is null )
770+ if ( Type is null )
760771 {
761- if ( version is OpenApiSpecVersion . OpenApi3_0 && isNullable )
762- {
763- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
764- }
772+ return ;
765773 }
766- else if ( ! HasMultipleTypes ( type . Value ) )
767- {
768- switch ( version )
769- {
770- case OpenApiSpecVersion . OpenApi3_1 when isNullable :
771- UpCastSchemaTypeToV31 ( type . Value , writer ) ;
772- break ;
773- case OpenApiSpecVersion . OpenApi3_0 when isNullable && type . Value == JsonSchemaType . Null :
774- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
775- break ;
776- case OpenApiSpecVersion . OpenApi3_0 when isNullable && type . Value != JsonSchemaType . Null :
777- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
778- writer . WriteProperty ( OpenApiConstants . Type , type . Value . ToFirstIdentifier ( ) ) ;
779- break ;
780- default :
781- writer . WriteProperty ( OpenApiConstants . Type , type . Value . ToFirstIdentifier ( ) ) ;
782- break ;
783- }
784- }
785- else
774+
775+ var unifiedType = IsNullable ? Type . Value | JsonSchemaType . Null : Type . Value ;
776+ var typeWithoutNull = unifiedType & ~ JsonSchemaType . Null ;
777+
778+ switch ( version )
786779 {
787- // type
788- if ( version is OpenApiSpecVersion . OpenApi2_0 || version is OpenApiSpecVersion . OpenApi3_0 )
789- {
790- DowncastTypeArrayToV2OrV3 ( type . Value , writer , version ) ;
791- }
792- else
793- {
794- var list = ( from JsonSchemaType flag in jsonSchemaTypeValues
795- where type . Value . HasFlag ( flag )
796- select flag ) . ToList ( ) ;
797- writer . WriteOptionalCollection ( OpenApiConstants . Type , list , ( w , s ) =>
780+ case OpenApiSpecVersion . OpenApi2_0 or OpenApiSpecVersion . OpenApi3_0 :
781+ if ( typeWithoutNull != 0 && ! HasMultipleTypes ( typeWithoutNull ) )
798782 {
799- foreach ( var item in s . ToIdentifiers ( ) )
800- {
801- w . WriteValue ( item ) ;
802- }
803- } ) ;
804- }
783+ writer . WriteProperty ( OpenApiConstants . Type , typeWithoutNull . ToFirstIdentifier ( ) ) ;
784+ }
785+ break ;
786+ default :
787+ WriteUnifiedSchemaType ( unifiedType , writer ) ;
788+ break ;
805789 }
806790 }
807791
@@ -813,20 +797,17 @@ private static bool IsPowerOfTwo(int x)
813797 private static bool HasMultipleTypes ( JsonSchemaType schemaType )
814798 {
815799 var schemaTypeNumeric = ( int ) schemaType ;
816- return ! IsPowerOfTwo ( schemaTypeNumeric ) && // Boolean, Integer, Number, String, Array, Object
817- schemaTypeNumeric != ( int ) JsonSchemaType . Null ;
800+ return ! IsPowerOfTwo ( schemaTypeNumeric ) ;
818801 }
819802
820- private static void UpCastSchemaTypeToV31 ( JsonSchemaType type , IOpenApiWriter writer )
803+ private static void WriteUnifiedSchemaType ( JsonSchemaType type , IOpenApiWriter writer )
821804 {
822- // create a new array and insert the type and "null" as values
823- var temporaryType = type | JsonSchemaType . Null ;
824- var list = ( from JsonSchemaType flag in jsonSchemaTypeValues // Check if the flag is set in 'type' using a bitwise AND operation
825- where temporaryType . HasFlag ( flag )
826- select flag . ToFirstIdentifier ( ) ) . ToList ( ) ;
827- if ( list . Count > 1 )
805+ var array = ( from JsonSchemaType flag in jsonSchemaTypeValues
806+ where type . HasFlag ( flag )
807+ select flag . ToFirstIdentifier ( ) ) . ToArray ( ) ;
808+ if ( array . Length > 1 )
828809 {
829- writer . WriteOptionalCollection ( OpenApiConstants . Type , list , ( w , s ) =>
810+ writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) =>
830811 {
831812 if ( ! string . IsNullOrEmpty ( s ) && s is not null )
832813 {
@@ -836,54 +817,32 @@ where temporaryType.HasFlag(flag)
836817 }
837818 else
838819 {
839- writer . WriteProperty ( OpenApiConstants . Type , list [ 0 ] ) ;
820+ writer . WriteProperty ( OpenApiConstants . Type , array [ 0 ] ) ;
840821 }
841822 }
842823
843- #if NET5_0_OR_GREATER
844- private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues < JsonSchemaType > ( ) ;
845- #else
846- private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues ( typeof ( JsonSchemaType ) ) ;
847- #endif
848-
849- private static void DowncastTypeArrayToV2OrV3 ( JsonSchemaType schemaType , IOpenApiWriter writer , OpenApiSpecVersion version )
824+ private void SerializeNullable ( IOpenApiWriter writer , OpenApiSpecVersion version )
850825 {
851- /* If the array has one non-null value, emit Type as string
852- * If the array has one null value, emit x-nullable as true
853- * If the array has two values, one null and one non-null, emit Type as string and x-nullable as true
854- * If the array has more than two values or two non-null values, do not emit type
855- * */
856-
857- var nullableProp = version . Equals ( OpenApiSpecVersion . OpenApi2_0 )
858- ? OpenApiConstants . NullableExtension
859- : OpenApiConstants . Nullable ;
860-
861- if ( ! HasMultipleTypes ( schemaType & ~ JsonSchemaType . Null ) && ( schemaType & JsonSchemaType . Null ) == JsonSchemaType . Null ) // checks for two values and one is null
862- {
863- foreach ( JsonSchemaType flag in jsonSchemaTypeValues )
864- {
865- // Skip if the flag is not set or if it's the Null flag
866- if ( schemaType . HasFlag ( flag ) && flag != JsonSchemaType . Null )
867- {
868- // Write the non-null flag value to the writer
869- writer . WriteProperty ( OpenApiConstants . Type , flag . ToFirstIdentifier ( ) ) ;
870- }
871- }
872- writer . WriteProperty ( nullableProp , true ) ;
873- }
874- else if ( ! HasMultipleTypes ( schemaType ) )
826+ if ( IsNullable )
875827 {
876- if ( schemaType is JsonSchemaType . Null )
877- {
878- writer . WriteProperty ( nullableProp , true ) ;
879- }
880- else
828+ switch ( version )
881829 {
882- writer . WriteProperty ( OpenApiConstants . Type , schemaType . ToFirstIdentifier ( ) ) ;
830+ case OpenApiSpecVersion . OpenApi2_0 :
831+ writer . WriteProperty ( OpenApiConstants . NullableExtension , true ) ;
832+ break ;
833+ case OpenApiSpecVersion . OpenApi3_0 :
834+ writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
835+ break ;
883836 }
884837 }
885838 }
886839
840+ #if NET5_0_OR_GREATER
841+ private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues < JsonSchemaType > ( ) ;
842+ #else
843+ private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues ( typeof ( JsonSchemaType ) ) ;
844+ #endif
845+
887846 /// <inheritdoc/>
888847 public IOpenApiSchema CreateShallowCopy ( )
889848 {
0 commit comments