Skip to content

Commit 2b9d7f4

Browse files
authored
Merge pull request #2853 from microsoft/copilot/fix-openapi-deserializer-nullable-issue
fix(reader): preserve Null flag when nullable appears before type in V3.0/V3.1/V3.2 deserializers
2 parents 0f8bffa + de72b1d commit 2b9d7f4

5 files changed

Lines changed: 46 additions & 4 deletions

File tree

src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,17 @@ internal static partial class OpenApiV31Deserializer
198198
"type",
199199
(o, n, doc) =>
200200
{
201+
// Preserve any Null flag set by a preceding "nullable: true" handler
202+
var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null);
201203
if (n is ValueNode)
202204
{
203-
o.Type = n.GetScalarValue()?.ToJsonSchemaType();
205+
var parsedType = n.GetScalarValue()?.ToJsonSchemaType();
206+
o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType;
204207
}
205208
else
206209
{
207210
var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc);
208-
JsonSchemaType combinedType = 0;
211+
JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0;
209212
foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType()))
210213
{
211214
combinedType |= type;

src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,17 @@ internal static partial class OpenApiV32Deserializer
198198
"type",
199199
(o, n, doc) =>
200200
{
201+
// Preserve any Null flag set by a preceding "nullable: true" handler
202+
var preserveNull = o.Type.HasValue && o.Type.Value.HasFlag(JsonSchemaType.Null);
201203
if (n is ValueNode)
202204
{
203-
o.Type = n.GetScalarValue()?.ToJsonSchemaType();
205+
var parsedType = n.GetScalarValue()?.ToJsonSchemaType();
206+
o.Type = preserveNull ? parsedType | JsonSchemaType.Null : parsedType;
204207
}
205208
else
206209
{
207210
var list = n.CreateSimpleList((n2, p) => n2.GetScalarValue(), doc);
208-
JsonSchemaType combinedType = 0;
211+
JsonSchemaType combinedType = preserveNull ? JsonSchemaType.Null : 0;
209212
foreach(var type in list.Where(static t => t is not null).Select(static t => t!.ToJsonSchemaType()))
210213
{
211214
combinedType |= type;

test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,18 @@ public void ParseSchemaWithTypeArrayWorks()
132132
Assert.Equivalent(expected, actual);
133133
}
134134

135+
[Theory]
136+
[InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")]
137+
[InlineData(@"{ ""type"": ""string"", ""nullable"": true }")]
138+
public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson)
139+
{
140+
// Act
141+
var schema = OpenApiModelFactory.Parse<OpenApiSchema>(schemaJson, OpenApiSpecVersion.OpenApi3_1, new(), out _, "json", SettingsFixture.ReaderSettings);
142+
143+
// Assert
144+
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type);
145+
}
146+
135147
[Fact]
136148
public void TestSchemaCopyConstructorWithTypeArrayWorks()
137149
{

test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public void ParseSchemaWithTypeArrayWorks()
131131
Assert.Equivalent(expected, actual);
132132
}
133133

134+
[Theory]
135+
[InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")]
136+
[InlineData(@"{ ""type"": ""string"", ""nullable"": true }")]
137+
public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson)
138+
{
139+
// Act
140+
var schema = OpenApiModelFactory.Parse<OpenApiSchema>(schemaJson, OpenApiSpecVersion.OpenApi3_2, new(), out _, "json", SettingsFixture.ReaderSettings);
141+
142+
// Assert
143+
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type);
144+
}
145+
134146
[Fact]
135147
public void TestSchemaCopyConstructorWithTypeArrayWorks()
136148
{

test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ public void ParsePathFragmentShouldSucceed()
133133
}, openApiAny);
134134
}
135135

136+
[Theory]
137+
[InlineData(@"{ ""nullable"": true, ""type"": ""string"" }")]
138+
[InlineData(@"{ ""type"": ""string"", ""nullable"": true }")]
139+
public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string schemaJson)
140+
{
141+
// Act
142+
var schema = OpenApiModelFactory.Parse<OpenApiSchema>(schemaJson, OpenApiSpecVersion.OpenApi3_0, new(), out _, "json", SettingsFixture.ReaderSettings);
143+
144+
// Assert
145+
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, schema.Type);
146+
}
147+
136148
[Fact]
137149
public void ParseDictionarySchemaShouldSucceed()
138150
{

0 commit comments

Comments
 (0)