@@ -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,68 +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
774
769
- switch ( version )
770
- {
771
- case OpenApiSpecVersion . OpenApi3_1 when isNullable :
772
- UpCastSchemaTypeToV31 ( type . Value , writer ) ;
773
- break ;
774
- case OpenApiSpecVersion . OpenApi3_0 when isNullable && type . Value == JsonSchemaType . Null :
775
- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
776
- writer . WriteProperty ( OpenApiConstants . Type , JsonSchemaType . Object . ToFirstIdentifier ( ) ) ;
777
- break ;
778
- case OpenApiSpecVersion . OpenApi3_0 when isNullable && type . Value != JsonSchemaType . Null :
779
- writer . WriteProperty ( OpenApiConstants . Nullable , true ) ;
780
- writer . WriteProperty ( OpenApiConstants . Type , type . Value . ToFirstIdentifier ( ) ) ;
781
- break ;
782
- default :
783
- writer . WriteProperty ( OpenApiConstants . Type , type . Value . ToFirstIdentifier ( ) ) ;
784
- break ;
785
- }
786
- }
787
- else
775
+ var unifiedType = IsNullable ? Type . Value | JsonSchemaType . Null : Type . Value ;
776
+ var typeWithoutNull = unifiedType & ~ JsonSchemaType . Null ;
777
+
778
+ switch ( version )
788
779
{
789
- // type
790
- if ( version is OpenApiSpecVersion . OpenApi2_0 || version is OpenApiSpecVersion . OpenApi3_0 )
791
- {
792
- DowncastTypeArrayToV2OrV3 ( type . Value , writer , version ) ;
793
- }
794
- else
795
- {
796
- var list = ( from JsonSchemaType flag in jsonSchemaTypeValues
797
- where type . Value . HasFlag ( flag )
798
- select flag ) . ToList ( ) ;
799
- writer . WriteOptionalCollection ( OpenApiConstants . Type , list , ( w , s ) =>
780
+ case OpenApiSpecVersion . OpenApi2_0 or OpenApiSpecVersion . OpenApi3_0 :
781
+ if ( typeWithoutNull != 0 && ! HasMultipleTypes ( typeWithoutNull ) )
800
782
{
801
- foreach ( var item in s . ToIdentifiers ( ) )
802
- {
803
- w . WriteValue ( item ) ;
804
- }
805
- } ) ;
806
- }
783
+ writer . WriteProperty ( OpenApiConstants . Type , typeWithoutNull . ToFirstIdentifier ( ) ) ;
784
+ }
785
+ break ;
786
+ default :
787
+ WriteUnifiedSchemaType ( unifiedType , writer ) ;
788
+ break ;
807
789
}
808
790
}
809
791
@@ -815,20 +797,17 @@ private static bool IsPowerOfTwo(int x)
815
797
private static bool HasMultipleTypes ( JsonSchemaType schemaType )
816
798
{
817
799
var schemaTypeNumeric = ( int ) schemaType ;
818
- return ! IsPowerOfTwo ( schemaTypeNumeric ) && // Boolean, Integer, Number, String, Array, Object
819
- schemaTypeNumeric != ( int ) JsonSchemaType . Null ;
800
+ return ! IsPowerOfTwo ( schemaTypeNumeric ) ;
820
801
}
821
802
822
- private static void UpCastSchemaTypeToV31 ( JsonSchemaType type , IOpenApiWriter writer )
803
+ private static void WriteUnifiedSchemaType ( JsonSchemaType type , IOpenApiWriter writer )
823
804
{
824
- // create a new array and insert the type and "null" as values
825
- var temporaryType = type | JsonSchemaType . Null ;
826
- var list = ( from JsonSchemaType flag in jsonSchemaTypeValues // Check if the flag is set in 'type' using a bitwise AND operation
827
- where temporaryType . HasFlag ( flag )
828
- select flag . ToFirstIdentifier ( ) ) . ToList ( ) ;
829
- 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 )
830
809
{
831
- writer . WriteOptionalCollection ( OpenApiConstants . Type , list , ( w , s ) =>
810
+ writer . WriteOptionalCollection ( OpenApiConstants . Type , array , ( w , s ) =>
832
811
{
833
812
if ( ! string . IsNullOrEmpty ( s ) && s is not null )
834
813
{
@@ -838,54 +817,32 @@ where temporaryType.HasFlag(flag)
838
817
}
839
818
else
840
819
{
841
- writer . WriteProperty ( OpenApiConstants . Type , list [ 0 ] ) ;
820
+ writer . WriteProperty ( OpenApiConstants . Type , array [ 0 ] ) ;
842
821
}
843
822
}
844
823
845
- #if NET5_0_OR_GREATER
846
- private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues < JsonSchemaType > ( ) ;
847
- #else
848
- private static readonly Array jsonSchemaTypeValues = System . Enum . GetValues ( typeof ( JsonSchemaType ) ) ;
849
- #endif
850
-
851
- private static void DowncastTypeArrayToV2OrV3 ( JsonSchemaType schemaType , IOpenApiWriter writer , OpenApiSpecVersion version )
824
+ private void SerializeNullable ( IOpenApiWriter writer , OpenApiSpecVersion version )
852
825
{
853
- /* If the array has one non-null value, emit Type as string
854
- * If the array has one null value, emit x-nullable as true
855
- * If the array has two values, one null and one non-null, emit Type as string and x-nullable as true
856
- * If the array has more than two values or two non-null values, do not emit type
857
- * */
858
-
859
- var nullableProp = version . Equals ( OpenApiSpecVersion . OpenApi2_0 )
860
- ? OpenApiConstants . NullableExtension
861
- : OpenApiConstants . Nullable ;
862
-
863
- if ( ! HasMultipleTypes ( schemaType & ~ JsonSchemaType . Null ) && ( schemaType & JsonSchemaType . Null ) == JsonSchemaType . Null ) // checks for two values and one is null
864
- {
865
- foreach ( JsonSchemaType flag in jsonSchemaTypeValues )
866
- {
867
- // Skip if the flag is not set or if it's the Null flag
868
- if ( schemaType . HasFlag ( flag ) && flag != JsonSchemaType . Null )
869
- {
870
- // Write the non-null flag value to the writer
871
- writer . WriteProperty ( OpenApiConstants . Type , flag . ToFirstIdentifier ( ) ) ;
872
- }
873
- }
874
- writer . WriteProperty ( nullableProp , true ) ;
875
- }
876
- else if ( ! HasMultipleTypes ( schemaType ) )
826
+ if ( IsNullable )
877
827
{
878
- if ( schemaType is JsonSchemaType . Null )
879
- {
880
- writer . WriteProperty ( nullableProp , true ) ;
881
- }
882
- else
828
+ switch ( version )
883
829
{
884
- 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 ;
885
836
}
886
837
}
887
838
}
888
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
+
889
846
/// <inheritdoc/>
890
847
public IOpenApiSchema CreateShallowCopy ( )
891
848
{
0 commit comments