@@ -104,6 +104,14 @@ public string? ExclusiveMinimum
104
104
/// <inheritdoc />
105
105
public JsonSchemaType ? Type { get ; set ; }
106
106
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
+
107
115
/// <inheritdoc />
108
116
public string ? Const { get ; set ; }
109
117
@@ -437,7 +445,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
437
445
writer . WriteOptionalCollection ( OpenApiConstants . Enum , Enum , ( nodeWriter , s ) => nodeWriter . WriteAny ( s ) ) ;
438
446
439
447
// type
440
- SerializeTypeProperty ( Type , writer , version ) ;
448
+ SerializeTypeProperty ( writer , version ) ;
441
449
442
450
// allOf
443
451
writer . WriteOptionalCollection ( OpenApiConstants . AllOf , AllOf , callback ) ;
@@ -479,6 +487,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
479
487
// default
480
488
writer . WriteOptionalObject ( OpenApiConstants . Default , Default , ( w , d ) => w . WriteAny ( d ) ) ;
481
489
490
+ // nullable
491
+ if ( version == OpenApiSpecVersion . OpenApi3_0 )
492
+ {
493
+ SerializeNullable ( writer , version ) ;
494
+ }
495
+
482
496
// discriminator
483
497
writer . WriteOptionalObject ( OpenApiConstants . Discriminator , Discriminator , callback ) ;
484
498
@@ -619,7 +633,7 @@ private void SerializeAsV2(
619
633
writer . WriteStartObject ( ) ;
620
634
621
635
// type
622
- SerializeTypeProperty ( Type , writer , OpenApiSpecVersion . OpenApi2_0 ) ;
636
+ SerializeTypeProperty ( writer , OpenApiSpecVersion . OpenApi2_0 ) ;
623
637
624
638
// description
625
639
writer . WriteProperty ( OpenApiConstants . Description , Description ) ;
@@ -742,66 +756,36 @@ private void SerializeAsV2(
742
756
// example
743
757
writer . WriteOptionalObject ( OpenApiConstants . Example , Example , ( w , e ) => w . WriteAny ( e ) ) ;
744
758
759
+ // x-nullable extension
760
+ SerializeNullable ( writer , OpenApiSpecVersion . OpenApi2_0 ) ;
761
+
745
762
// extensions
746
763
writer . WriteExtensions ( Extensions , OpenApiSpecVersion . OpenApi2_0 ) ;
747
764
748
765
writer . WriteEndObject ( ) ;
749
766
}
750
767
751
- private void SerializeTypeProperty ( JsonSchemaType ? type , IOpenApiWriter writer , OpenApiSpecVersion version )
768
+ private void SerializeTypeProperty ( IOpenApiWriter writer , OpenApiSpecVersion version )
752
769
{
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 )
760
771
{
761
- if ( version is OpenApiSpecVersion . OpenApi3_0 && isNullable )
762
- {
763
- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
764
- }
772
+ return ;
765
773
}
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 )
786
779
{
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 ) )
798
782
{
799
- foreach ( var item in s . ToIdentifiers ( ) )
800
- {
801
- w . WriteValue ( item ) ;
802
- }
803
- } ) ;
804
- }
783
+ writer . WriteProperty ( OpenApiConstants . Type , Type . Value . ToFirstIdentifier ( ) ) ;
784
+ }
785
+ break ;
786
+ default :
787
+ WriteUnifiedSchemaType ( unifiedType , writer ) ;
788
+ break ;
805
789
}
806
790
}
807
791
@@ -813,20 +797,17 @@ private static bool IsPowerOfTwo(int x)
813
797
private static bool HasMultipleTypes ( JsonSchemaType schemaType )
814
798
{
815
799
var schemaTypeNumeric = ( int ) schemaType ;
816
- return ! IsPowerOfTwo ( schemaTypeNumeric ) && // Boolean, Integer, Number, String, Array, Object
817
- schemaTypeNumeric != ( int ) JsonSchemaType . Null ;
800
+ return ! IsPowerOfTwo ( schemaTypeNumeric ) ;
818
801
}
819
802
820
- private static void UpCastSchemaTypeToV31 ( JsonSchemaType type , IOpenApiWriter writer )
803
+ private static void WriteUnifiedSchemaType ( JsonSchemaType type , IOpenApiWriter writer )
821
804
{
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 )
828
809
{
829
- writer . WriteOptionalCollection ( OpenApiConstants . Type , list , ( w , s ) =>
810
+ writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) =>
830
811
{
831
812
if ( ! string . IsNullOrEmpty ( s ) && s is not null )
832
813
{
@@ -836,54 +817,32 @@ where temporaryType.HasFlag(flag)
836
817
}
837
818
else
838
819
{
839
- writer . WriteProperty ( OpenApiConstants . Type , list [ 0 ] ) ;
820
+ writer . WriteProperty ( OpenApiConstants . Type , array [ 0 ] ) ;
840
821
}
841
822
}
842
823
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 )
850
825
{
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 )
875
827
{
876
- if ( schemaType is JsonSchemaType . Null )
877
- {
878
- writer . WriteProperty ( nullableProp , true ) ;
879
- }
880
- else
828
+ switch ( version )
881
829
{
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 ;
883
836
}
884
837
}
885
838
}
886
839
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
+
887
846
/// <inheritdoc/>
888
847
public IOpenApiSchema CreateShallowCopy ( )
889
848
{
0 commit comments