diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 9d55602b3..4d53ec759 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -20,7 +20,7 @@ jobs:
- name: Checkout repository
id: checkout_repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
@@ -41,7 +41,7 @@ jobs:
name: Validate Project for Trimming
runs-on: windows-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
@@ -58,7 +58,7 @@ jobs:
needs: [ci]
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 72eea8bbf..30c8acef4 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout repository
id: checkout_repo
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml
index 9c76a0d64..c905a3f48 100644
--- a/.github/workflows/release-please-gha.yml
+++ b/.github/workflows/release-please-gha.yml
@@ -16,6 +16,7 @@ on:
- main
- support/v1
- support/v2
+ workflow_dispatch:
permissions:
contents: read
@@ -24,7 +25,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Generate GitHub App token
id: app-token
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 51f9b5986..3f969e825 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -43,7 +43,7 @@ jobs:
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 56441f9d3..a1304a172 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.0.1"
+ ".": "3.0.2"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98861fe62..83c66a66d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,14 @@
# Changelog
-## [3.0.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.0...v3.0.1) (2025-11-17)
+## [3.0.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.1...v3.0.2) (2025-12-08)
+
+
+### Bug Fixes
+
+* additional properties serialization should not emit a schema in v2 ([946cba9](https://github.com/microsoft/OpenAPI.NET/commit/946cba992a2733a60182453e38722b4ed789b729))
+* additional properties serialization should not emit booleans in v3.1+ ([946cba9](https://github.com/microsoft/OpenAPI.NET/commit/946cba992a2733a60182453e38722b4ed789b729))
+## [3.0.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.0.0...v3.0.1) (2025-11-17)
### Bug Fixes
@@ -10,7 +17,6 @@
## [3.0.0](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.9...v3.0.0) (2025-11-11)
-
### ⚠ BREAKING CHANGES
* adds support for OpenAPI 3.2.0
@@ -23,8 +29,12 @@
* adds support for OpenAPI 3.2.0 ([765a8dd](https://github.com/microsoft/OpenAPI.NET/commit/765a8dd4d6efd1a31b6a76d282ccffa5877a845a))
-## [2.3.9](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.8...v2.3.9) (2025-11-06)
+## [2.3.10](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.9...v2.3.10) (2025-11-17)
+* empty strings should be quoted in yaml ([e919b33](https://github.com/microsoft/OpenAPI.NET/commit/e919b33e9d09159217066248483ef4c767865c82))
+* empty strings should be quoted in yaml ([0ca10db](https://github.com/microsoft/OpenAPI.NET/commit/0ca10db3bb9ffa937dd35862068926f3586d6991))
+
+## [2.3.9](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.8...v2.3.9) (2025-11-06)
### Bug Fixes
diff --git a/Directory.Build.props b/Directory.Build.props
index 476cfb435..8582f5826 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,7 +12,7 @@
https://github.com/Microsoft/OpenAPI.NET
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
- 3.0.1
+ 3.0.2
diff --git a/performance/benchmark/PerformanceTests.csproj b/performance/benchmark/PerformanceTests.csproj
index 53c0784d7..89b757f90 100644
--- a/performance/benchmark/PerformanceTests.csproj
+++ b/performance/benchmark/PerformanceTests.csproj
@@ -15,8 +15,8 @@
CA1822;CS9057;
-
-
+
+
diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs
index c371e9786..1bb7639da 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs
@@ -471,17 +471,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);
@@ -732,14 +735,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/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
index aac7c907f..e0f481858 100644
--- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
index 87dc85ced..04406a370 100644
--- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
@@ -684,6 +684,114 @@ 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)]
+ [InlineData(OpenApiSpecVersion.OpenApi3_2)]
+ 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)]
+ [InlineData(OpenApiSpecVersion.OpenApi3_2)]
+ 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
{