diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index db3064b00..f31644c1f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -466,17 +466,20 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, callback); // additionalProperties - if (AdditionalPropertiesAllowed) + if (AdditionalProperties is not null && version >= OpenApiSpecVersion.OpenApi3_0) { writer.WriteOptionalObject( OpenApiConstants.AdditionalProperties, AdditionalProperties, callback); } - else + // true is the default in earlier versions 3, no need to write it out + // boolean value is only supported for version 3 and earlier (version 2 is implemented in the other serialize method, the condition is a failsafe) + else if (!AdditionalPropertiesAllowed && version <= OpenApiSpecVersion.OpenApi3_0) { writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); } + // not having anything is the same as having it set to true (v2/v3) or an empty schema (v3.1+) // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -727,14 +730,9 @@ private void SerializeAsV2( }); // additionalProperties - if (AdditionalPropertiesAllowed) - { - writer.WriteOptionalObject( - OpenApiConstants.AdditionalProperties, - AdditionalProperties, - (w, s) => s.SerializeAsV2(w)); - } - else + // a schema cannot be serialized in v2 + // true is the default, no need to write it out + if (!AdditionalPropertiesAllowed) { writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed); } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 87dc85ced..6ffbb9fe4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -684,6 +684,112 @@ public async Task SerializeConstAsEnumV20() Assert.False(v2Node.AsObject().ContainsKey("const")); } + [Fact] + public async Task SerializeAdditionalPropertiesAsV2DoesNotEmit() + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalProperties = new OpenApiSchema() + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV2DefaultDoesNotEmit() + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = true + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV2FalseEmits() + { + var expected = @"{ ""additionalProperties"": false }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + public async Task SerializeAdditionalPropertiesAllowedAsV3PlusDefaultDoesNotEmit(OpenApiSpecVersion version) + { + var expected = @"{ }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = true + }; + + // When + var actual = await schema.SerializeAsJsonAsync(version); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeAdditionalPropertiesAllowedAsV3FalseEmits() + { + var expected = @"{ ""additionalProperties"": false }"; + // Given + var schema = new OpenApiSchema + { + AdditionalPropertiesAllowed = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + public async Task SerializeAdditionalPropertiesAsV3PlusEmits(OpenApiSpecVersion version) + { + var expected = @"{ ""additionalProperties"": { } }"; + // Given + var schema = new OpenApiSchema + { + AdditionalProperties = new OpenApiSchema() + }; + + // When + var actual = await schema.SerializeAsJsonAsync(version); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + internal class SchemaVisitor : OpenApiVisitorBase {