diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 39c32c979..296626a93 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -198,14 +198,17 @@ internal static partial class OpenApiV31Deserializer "type", (o, n, doc) => { + // Preserve any Null flag set by a preceding "nullable: true" handler + var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null); if (n is ValueNode) { - o.Type = n.GetScalarValue()?.ToJsonSchemaType(); + var parsedType = n.GetScalarValue()?.ToJsonSchemaType(); + o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType; } else { var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc); - JsonSchemaType combinedType = 0; + JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0; foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType())) { combinedType |= type; diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index c18c48462..f0df47b99 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -198,14 +198,17 @@ internal static partial class OpenApiV32Deserializer "type", (o, n, doc) => { + // Preserve any Null flag set by a preceding "nullable: true" handler + var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null); if (n is ValueNode) { - o.Type = n.GetScalarValue()?.ToJsonSchemaType(); + var parsedType = n.GetScalarValue()?.ToJsonSchemaType(); + o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType; } else { var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc); - JsonSchemaType combinedType = 0; + JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0; foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType())) { combinedType |= type; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index b65b2d8c3..ec66dcbb9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -132,6 +132,18 @@ public void ParseSchemaWithTypeArrayWorks() Assert.Equivalent(expected, actual); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_1, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void TestSchemaCopyConstructorWithTypeArrayWorks() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index 7f91b0327..621cd156c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -131,6 +131,18 @@ public void ParseSchemaWithTypeArrayWorks() Assert.Equivalent(expected, actual); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_2, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void TestSchemaCopyConstructorWithTypeArrayWorks() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index e16e40594..8241e55ed 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -133,6 +133,18 @@ public void ParsePathFragmentShouldSucceed() }, openApiAny); } + [Theory] + [InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")] + [InlineData(@"{ ""type"": ""string"", ""nullable"": true }")] + public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson) + { + // Act + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_0, new(), out _, "json", SettingsFixture.ReaderSettings); + + // Assert + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type); + } + [Fact] public void ParseDictionarySchemaShouldSucceed() {