Skip to content

Commit 1540ae4

Browse files
committed
Round-tripping
1 parent 21578cf commit 1540ae4

3 files changed

Lines changed: 295 additions & 4 deletions

File tree

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,47 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
420420
schema.Type = JsonSchemaType.Null;
421421
}
422422

423+
if (schema.Type is null)
424+
{
425+
if (schema.AnyOf is not null &&
426+
schema.AnyOf.All(childSchema => childSchema is OpenApiSchema schema && DoesSchemaRepresentSingleType(schema)))
427+
{
428+
JsonSchemaType types = GetAllTypes(schema.AnyOf);
429+
schema.AnyOf = null;
430+
schema.Type = types;
431+
}
432+
else if (schema.OneOf is not null &&
433+
schema.OneOf.All(childSchema => childSchema is OpenApiSchema schema && DoesSchemaRepresentSingleType(schema)))
434+
{
435+
JsonSchemaType types = GetAllTypes(schema.OneOf);
436+
schema.OneOf = null;
437+
schema.Type = types;
438+
}
439+
}
440+
423441
return schema;
424442
}
443+
444+
private static JsonSchemaType GetAllTypes(IList<IOpenApiSchema> schemas)
445+
{
446+
JsonSchemaType types = 0;
447+
foreach (var schema in schemas)
448+
{
449+
types |= schema.Type!.Value;
450+
}
451+
452+
return types;
453+
}
454+
455+
private static bool DoesSchemaRepresentSingleType(OpenApiSchema schema)
456+
{
457+
return schema.Type is JsonSchemaType.Null or
458+
JsonSchemaType.Boolean or
459+
JsonSchemaType.Integer or
460+
JsonSchemaType.Number or
461+
JsonSchemaType.String or
462+
JsonSchemaType.Object or
463+
JsonSchemaType.Array;
464+
}
425465
}
426466
}

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

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Text.Json.Serialization;
1212
using System.Threading.Tasks;
1313
using FluentAssertions;
14+
using Microsoft.OpenApi.Reader;
1415
using VerifyXunit;
1516
using Xunit;
1617

@@ -2151,6 +2152,19 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_NotIncludingOne
21512152
""";
21522153

21532154
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2155+
2156+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2157+
Assert.Null(deserializedSchema.OneOf);
2158+
Assert.Null(deserializedSchema.AnyOf);
2159+
Assert.Equal(schema.Type, deserializedSchema.Type);
2160+
2161+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2162+
var expected32 = """
2163+
{
2164+
"type": [ "integer", "string" ]
2165+
}
2166+
""";
2167+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
21542168
}
21552169

21562170
[Fact]
@@ -2194,6 +2208,27 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingOneOfB
21942208
""";
21952209

21962210
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2211+
2212+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2213+
Assert.NotNull(deserializedSchema.OneOf);
2214+
Assert.Null(deserializedSchema.AnyOf);
2215+
Assert.Equal(schema.Type, deserializedSchema.Type);
2216+
2217+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2218+
var expected32 = """
2219+
{
2220+
"type": [ "integer", "string" ],
2221+
"oneOf": [
2222+
{
2223+
"enum": [
2224+
"A",
2225+
"B"
2226+
]
2227+
}
2228+
]
2229+
}
2230+
""";
2231+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
21972232
}
21982233

21992234
[Fact]
@@ -2237,6 +2272,27 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingAnyOfB
22372272
""";
22382273

22392274
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2275+
2276+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2277+
Assert.Null(deserializedSchema.OneOf);
2278+
Assert.NotNull(deserializedSchema.AnyOf);
2279+
Assert.Equal(schema.Type, deserializedSchema.Type);
2280+
2281+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2282+
var expected32 = """
2283+
{
2284+
"type": [ "integer", "string" ],
2285+
"anyOf": [
2286+
{
2287+
"enum": [
2288+
"A",
2289+
"B"
2290+
]
2291+
}
2292+
]
2293+
}
2294+
""";
2295+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
22402296
}
22412297

22422298
[Fact]
@@ -2291,6 +2347,34 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingBothOn
22912347
""";
22922348

22932349
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2350+
2351+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2352+
Assert.NotNull(deserializedSchema.OneOf);
2353+
Assert.NotNull(deserializedSchema.AnyOf);
2354+
Assert.Null(deserializedSchema.Type);
2355+
2356+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2357+
var expected32 = """
2358+
{
2359+
"anyOf": [
2360+
{
2361+
"enum": [
2362+
"A",
2363+
"B"
2364+
]
2365+
}
2366+
],
2367+
"oneOf": [
2368+
{
2369+
"enum": [
2370+
"A",
2371+
"B"
2372+
]
2373+
}
2374+
]
2375+
}
2376+
""";
2377+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
22942378
}
22952379

22962380
[Fact]
@@ -2309,6 +2393,19 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_NotIn
23092393
""";
23102394

23112395
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2396+
2397+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2398+
Assert.Null(deserializedSchema.OneOf);
2399+
Assert.Null(deserializedSchema.AnyOf);
2400+
Assert.Equal(schema.Type, deserializedSchema.Type);
2401+
2402+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2403+
var expected32 = """
2404+
{
2405+
"type": [ "null", "string" ]
2406+
}
2407+
""";
2408+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
23122409
}
23132410

23142411
[Fact]
@@ -2346,6 +2443,27 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu
23462443
""";
23472444

23482445
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2446+
2447+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2448+
Assert.NotNull(deserializedSchema.OneOf);
2449+
Assert.Null(deserializedSchema.AnyOf);
2450+
Assert.Equal(schema.Type, deserializedSchema.Type);
2451+
2452+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2453+
var expected32 = """
2454+
{
2455+
"type": [ "null", "string" ],
2456+
"oneOf": [
2457+
{
2458+
"enum": [
2459+
"A",
2460+
"B"
2461+
]
2462+
}
2463+
]
2464+
}
2465+
""";
2466+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
23492467
}
23502468

23512469
[Fact]
@@ -2383,6 +2501,27 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu
23832501
""";
23842502

23852503
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2504+
2505+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2506+
Assert.Null(deserializedSchema.OneOf);
2507+
Assert.NotNull(deserializedSchema.AnyOf);
2508+
Assert.Equal(schema.Type, deserializedSchema.Type);
2509+
2510+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2511+
var expected32 = """
2512+
{
2513+
"type": [ "null", "string" ],
2514+
"anyOf": [
2515+
{
2516+
"enum": [
2517+
"A",
2518+
"B"
2519+
]
2520+
}
2521+
]
2522+
}
2523+
""";
2524+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
23862525
}
23872526

23882527
[Fact]
@@ -2439,6 +2578,35 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu
24392578
""";
24402579

24412580
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2581+
2582+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2583+
Assert.NotNull(deserializedSchema.OneOf);
2584+
Assert.NotNull(deserializedSchema.AnyOf);
2585+
Assert.Equal(schema.Type, deserializedSchema.Type);
2586+
2587+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2588+
var expected32 = """
2589+
{
2590+
"type": [ "null", "string" ],
2591+
"anyOf": [
2592+
{
2593+
"enum": [
2594+
"A",
2595+
"B"
2596+
]
2597+
}
2598+
],
2599+
"oneOf": [
2600+
{
2601+
"enum": [
2602+
"A",
2603+
"B"
2604+
]
2605+
}
2606+
]
2607+
}
2608+
""";
2609+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
24422610
}
24432611

24442612
[Fact]
@@ -2470,6 +2638,19 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_NotIncl
24702638
""";
24712639

24722640
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2641+
2642+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2643+
Assert.Null(deserializedSchema.OneOf);
2644+
Assert.Null(deserializedSchema.AnyOf);
2645+
Assert.Equal(schema.Type, deserializedSchema.Type);
2646+
2647+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2648+
var expected32 = """
2649+
{
2650+
"type": [ "null", "integer", "string" ]
2651+
}
2652+
""";
2653+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
24732654
}
24742655

24752656
[Fact]
@@ -2520,6 +2701,27 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi
25202701
""";
25212702

25222703
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2704+
2705+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2706+
Assert.NotNull(deserializedSchema.OneOf);
2707+
Assert.Null(deserializedSchema.AnyOf);
2708+
Assert.Equal(schema.Type, deserializedSchema.Type);
2709+
2710+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2711+
var expected32 = """
2712+
{
2713+
"type": [ "null", "integer", "string" ],
2714+
"oneOf": [
2715+
{
2716+
"enum": [
2717+
"A",
2718+
"B"
2719+
]
2720+
}
2721+
]
2722+
}
2723+
""";
2724+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
25232725
}
25242726

25252727
[Fact]
@@ -2570,6 +2772,27 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi
25702772
""";
25712773

25722774
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2775+
2776+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2777+
Assert.Null(deserializedSchema.OneOf);
2778+
Assert.NotNull(deserializedSchema.AnyOf);
2779+
Assert.Equal(schema.Type, deserializedSchema.Type);
2780+
2781+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2782+
var expected32 = """
2783+
{
2784+
"type": [ "null", "integer", "string" ],
2785+
"anyOf": [
2786+
{
2787+
"enum": [
2788+
"A",
2789+
"B"
2790+
]
2791+
}
2792+
]
2793+
}
2794+
""";
2795+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
25732796
}
25742797

25752798
[Fact]
@@ -2625,6 +2848,34 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi
26252848
""";
26262849

26272850
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result)));
2851+
2852+
var deserializedSchema = OpenApiModelFactory.Parse<OpenApiSchema>(expected, OpenApiSpecVersion.OpenApi3_0, new OpenApiDocument(), out _);
2853+
Assert.NotNull(deserializedSchema.OneOf);
2854+
Assert.NotNull(deserializedSchema.AnyOf);
2855+
Assert.Null(deserializedSchema.Type);
2856+
2857+
var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
2858+
var expected32 = """
2859+
{
2860+
"anyOf": [
2861+
{
2862+
"enum": [
2863+
"A",
2864+
"B"
2865+
]
2866+
}
2867+
],
2868+
"oneOf": [
2869+
{
2870+
"enum": [
2871+
"A",
2872+
"B"
2873+
]
2874+
}
2875+
]
2876+
}
2877+
""";
2878+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected32), JsonNode.Parse(actual32)));
26282879
}
26292880

26302881
private OpenApiSchema CreateNullableEnumSchema()

0 commit comments

Comments
 (0)