Skip to content

Commit b82d95f

Browse files
committed
Better nullability round-tripping
1 parent 870fce0 commit b82d95f

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
@@ -550,7 +550,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
550550
}
551551

552552
// type
553-
var serializedTypeProperty = TrySerializeTypeProperty(writer, version);
553+
SerializeTypeProperty(writer, version);
554554

555555
// allOf
556556
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, callback);
@@ -595,13 +595,13 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
595595
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
596596

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

@@ -838,7 +838,7 @@ private void SerializeAsV2(
838838
writer.WriteStartObject();
839839

840840
// type
841-
TrySerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0);
841+
SerializeTypeProperty(writer, OpenApiSpecVersion.OpenApi2_0);
842842

843843
// description
844844
writer.WriteProperty(OpenApiConstants.Description, Description);
@@ -1006,14 +1006,13 @@ private void SerializeAsV2(
10061006
writer.WriteEndObject();
10071007
}
10081008

1009-
private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version, JsonSchemaType? inferredType = null)
1009+
private void SerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion version)
10101010
{
1011-
// Use original type or inferred type when the explicit type is not set
1012-
var typeToUse = Type ?? inferredType;
1011+
var typeToUse = Type;
10131012

10141013
if (typeToUse is null)
10151014
{
1016-
return false;
1015+
return;
10171016
}
10181017

10191018
switch (version)
@@ -1023,15 +1022,15 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion
10231022
if (typeWithoutNull != 0 && !HasMultipleTypes(typeWithoutNull))
10241023
{
10251024
writer.WriteProperty(OpenApiConstants.Type, typeWithoutNull.ToFirstIdentifier());
1026-
return true;
1025+
return;
10271026
}
10281027
break;
10291028
default:
10301029
WriteUnifiedSchemaType(typeToUse.Value, writer);
1031-
return true;
1030+
return;
10321031
}
10331032

1034-
return false;
1033+
return;
10351034
}
10361035

10371036
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
@@ -965,7 +965,8 @@ public async Task SerializeOneOfWithNullAsV3ShouldUseNullableAsync()
965965
{
966966
"enum": [
967967
null
968-
]
968+
],
969+
"nullable": true
969970
},
970971
{
971972
"maxLength": 10,
@@ -1009,7 +1010,8 @@ public async Task SerializeOneOfWithNullAndMultipleSchemasAsV3ShouldMarkItAsNull
10091010
{
10101011
"enum": [
10111012
null
1012-
]
1013+
],
1014+
"nullable": true
10131015
},
10141016
{
10151017
"type": "string"
@@ -1061,7 +1063,8 @@ public async Task SerializeAnyOfWithNullAsV3ShouldUseNullableAsync()
10611063
{
10621064
"enum": [
10631065
null
1064-
]
1066+
],
1067+
"nullable": true
10651068
},
10661069
{
10671070
"type": "object",
@@ -1108,7 +1111,8 @@ public async Task SerializeAnyOfWithNullAndMultipleSchemasAsV3ShouldApplyNullabl
11081111
{
11091112
"enum": [
11101113
null
1111-
]
1114+
],
1115+
"nullable": true
11121116
},
11131117
{
11141118
"minLength": 1,
@@ -1153,7 +1157,8 @@ public async Task SerializeOneOfWithOnlyNullAsV3ShouldJustBeNullableAsync()
11531157
{
11541158
"enum": [
11551159
null
1156-
]
1160+
],
1161+
"nullable": true
11571162
}
11581163
]
11591164
}
@@ -1256,7 +1261,8 @@ public async Task SerializeOneOfWithNullAndRefAsV3ShouldUseNullableAsync()
12561261
{
12571262
"enum": [
12581263
null
1259-
]
1264+
],
1265+
"nullable": true
12601266
},
12611267
{
12621268
"$ref": "#/components/schemas/Pet"
@@ -2050,7 +2056,8 @@ public async Task SerializeNullableEnumWith3_0()
20502056
{
20512057
"enum": [
20522058
null
2053-
]
2059+
],
2060+
"nullable": true
20542061
},
20552062
{
20562063
"enum": [
@@ -2099,7 +2106,8 @@ public async Task SerializeNullableTypeWith3_0()
20992106
{
21002107
"enum": [
21012108
null
2102-
]
2109+
],
2110+
"nullable": true
21032111
}
21042112
""";
21052113

0 commit comments

Comments
 (0)