Skip to content

Commit 3f66f87

Browse files
authored
Merge pull request #3003 from Youssef1313/copilot/backport-pr-2994-to-support-v2
[support/v2] Better nullability round-tripping
2 parents 100804c + 7a25659 commit 3f66f87

5 files changed

Lines changed: 277 additions & 21 deletions

File tree

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
545545
}
546546

547547
// type
548-
var serializedTypeProperty = TrySerializeTypeProperty(writer, version);
548+
SerializeTypeProperty(writer, version);
549549

550550
// allOf
551551
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback);
@@ -590,13 +590,13 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
590590
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
591591

592592
// nullable
593-
if (version == OpenApiSpecVersion.OpenApi3_0 && serializedTypeProperty)
593+
if (version == OpenApiSpecVersion.OpenApi3_0)
594594
{
595595
// https://spec.openapis.org/oas/v3.0.4.html#fixed-fields-20
596596
// This keyword only takes effect if type is explicitly defined within the same Schema Object.
597597
//
598-
// If the user explicitly set IsNullable to true, we serialize it even if redundant.
599-
// But if **we** are inferring it (from oneOf/anyOf), we don't serialize it when it's redundant.
598+
// We don't care to avoid an unnecessary serialization.
599+
// So, we attempt to serialize it regardless of whether or not a type property was serialized.
600600
SerializeNullable(writer, version);
601601
}
602602

@@ -833,7 +833,7 @@ private void SerializeAsV2(
833833
writer.WriteStartObject();
834834

835835
// type
836-
TrySerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0);
836+
SerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0);
837837

838838
// description
839839
writer.WriteProperty(OpenApiConstants.Description, Description);
@@ -1001,14 +1001,13 @@ private void SerializeAsV2(
10011001
writer.WriteEndObject();
10021002
}
10031003

1004-
private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version, JsonSchemaType? inferredType = null)
1004+
private void SerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version)
10051005
{
1006-
// Use original type or inferred type when the explicit type is not set
1007-
var typeToUse = Type ?? inferredType;
1006+
var typeToUse = Type;
10081007

10091008
if (typeToUse is null)
10101009
{
1011-
return false;
1010+
return;
10121011
}
10131012

10141013
switch (version)
@@ -1018,15 +1017,15 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion
10181017
if (typeWithoutNull != 0 && !HasMultipleTypes(typeWithoutNull))
10191018
{
10201019
writer.WriteProperty(OpenApiConstants.Type, typeWithoutNull.ToFirstIdentifier());
1021-
return true;
1020+
return;
10221021
}
10231022
break;
10241023
default:
10251024
WriteUnifiedSchemaType(typeToUse.Value, writer);
1026-
return true;
1025+
return;
10271026
}
10281027

1029-
return false;
1028+
return;
10301029
}
10311030

10321031
private JsonNode? GetCompatibilityExample()

src/Microsoft.OpenApi/Reader/JsonNodeHelper.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ public static List<JsonNode> CreateListOfAny(this JsonNode? node, ParsingContext
4242
throw new OpenApiReaderException("Cannot create a list from this type of node.", context);
4343
}
4444

45-
return jsonArray.OfType<JsonNode>().ToList();
45+
var list = new List<JsonNode>(jsonArray.Count);
46+
foreach (var element in jsonArray)
47+
{
48+
list.Add(element ?? JsonNullSentinel.JsonNull);
49+
}
50+
51+
return list;
4652
}
4753

4854
public static List<T> CreateSimpleList<T>(this JsonNode? node, Func<JsonNode, OpenApiDocument?, T> map, OpenApiDocument? openApiDocument, ParsingContext context)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
413413
}
414414
}
415415

416+
if (schema.Type is null && schema.Enum is { Count: 1 } &&
417+
schema.Enum[0].IsJsonNullSentinel())
418+
{
419+
schema.Enum = null;
420+
schema.Type = JsonSchemaType.Null;
421+
}
422+
416423
return schema;
417424
}
418425
}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,8 @@ public async Task SerializeOneOfWithNullAsV3ShouldUseNullableAsync()
961961
{
962962
"enum": [
963963
null
964-
]
964+
],
965+
"nullable": true
965966
},
966967
{
967968
"maxLength": 10,
@@ -1005,7 +1006,8 @@ public async Task SerializeOneOfWithNullAndMultipleSchemasAsV3ShouldMarkItAsNull
10051006
{
10061007
"enum": [
10071008
null
1008-
]
1009+
],
1010+
"nullable": true
10091011
},
10101012
{
10111013
"type": "string"
@@ -1057,7 +1059,8 @@ public async Task SerializeAnyOfWithNullAsV3ShouldUseNullableAsync()
10571059
{
10581060
"enum": [
10591061
null
1060-
]
1062+
],
1063+
"nullable": true
10611064
},
10621065
{
10631066
"type": "object",
@@ -1104,7 +1107,8 @@ public async Task SerializeAnyOfWithNullAndMultipleSchemasAsV3ShouldApplyNullabl
11041107
{
11051108
"enum": [
11061109
null
1107-
]
1110+
],
1111+
"nullable": true
11081112
},
11091113
{
11101114
"minLength": 1,
@@ -1149,7 +1153,8 @@ public async Task SerializeOneOfWithOnlyNullAsV3ShouldJustBeNullableAsync()
11491153
{
11501154
"enum": [
11511155
null
1152-
]
1156+
],
1157+
"nullable": true
11531158
}
11541159
]
11551160
}
@@ -1252,7 +1257,8 @@ public async Task SerializeOneOfWithNullAndRefAsV3ShouldUseNullableAsync()
12521257
{
12531258
"enum": [
12541259
null
1255-
]
1260+
],
1261+
"nullable": true
12561262
},
12571263
{
12581264
"$ref": "#/components/schemas/Pet"
@@ -2043,7 +2049,8 @@ public async Task SerializeNullableEnumWith3_0()
20432049
{
20442050
"enum": [
20452051
null
2046-
]
2052+
],
2053+
"nullable": true
20472054
},
20482055
{
20492056
"enum": [
@@ -2091,7 +2098,8 @@ public async Task SerializeNullableTypeWith3_0()
20912098
{
20922099
"enum": [
20932100
null
2094-
]
2101+
],
2102+
"nullable": true
20952103
}
20962104
""";
20972105

0 commit comments

Comments
 (0)