diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9839c4904..a69dbcb16 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -29,7 +29,7 @@ jobs:
- name: Initialize CodeQL
id: init_codeql
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@v4
with:
queries: security-and-quality
@@ -49,6 +49,6 @@ jobs:
- name: Perform CodeQL Analysis
id: analyze_codeql
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@v4
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
\ No newline at end of file
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 8a45bd5dd..13d9f224f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.3.4"
+ ".": "2.3.6"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 361168645..d3b2d987e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Changelog
+## [2.3.6](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.5...v2.3.6) (2025-10-20)
+
+
+### Bug Fixes
+
+* a bug where empty collections would not be serialized for default values ([4c4d257](https://github.com/microsoft/OpenAPI.NET/commit/4c4d257c0cf10d1742fae9f3961e4a6242c0ce1d))
+
+## [2.3.5](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.4...v2.3.5) (2025-10-14)
+
+
+### Bug Fixes
+
+* use settings for terse output in serialization extension methods ([246039b](https://github.com/microsoft/OpenAPI.NET/commit/246039bfa8a16c042a10a87126289de82d18b321))
+* use settings for terse output in serialization extension methods ([8b91278](https://github.com/microsoft/OpenAPI.NET/commit/8b912788ef18b44a083d3fd2a1d6e25c9e6e17cb))
+
## [2.3.4](https://github.com/microsoft/OpenAPI.NET/compare/v2.3.3...v2.3.4) (2025-10-06)
diff --git a/Directory.Build.props b/Directory.Build.props
index c8f5a4612..6d13b2509 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
- 2.3.4
+ 2.3.6
diff --git a/global.json b/global.json
index da70a0aea..4c2f00caf 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "8.0.414"
+ "version": "8.0.415"
}
}
\ No newline at end of file
diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj
index 82a71aab7..c409872f6 100644
--- a/performance/resultsComparer/resultsComparer.csproj
+++ b/performance/resultsComparer/resultsComparer.csproj
@@ -8,12 +8,12 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
index 72a6485ea..7e61230e4 100644
--- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
+++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
@@ -29,10 +29,10 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
index 8787e7697..797ec2359 100644
--- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
+++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
@@ -192,16 +192,17 @@ private static async Task WriteOpenApiAsync(HidiOptions options, string openApiF
using var outputStream = options.Output.Create();
using var textWriter = new StreamWriter(outputStream);
- var settings = new OpenApiWriterSettings
+ var settings = new OpenApiJsonWriterSettings
{
InlineLocalReferences = options.InlineLocal,
- InlineExternalReferences = options.InlineExternal
+ InlineExternalReferences = options.InlineExternal,
+ Terse = options.TerseOutput
};
#pragma warning disable CA1308
IOpenApiWriter writer = openApiFormat.ToLowerInvariant() switch
#pragma warning restore CA1308
{
- OpenApiConstants.Json => options.TerseOutput ? new(textWriter, settings, options.TerseOutput) : new OpenApiJsonWriter(textWriter, settings, false),
+ OpenApiConstants.Json => new OpenApiJsonWriter(textWriter, settings),
OpenApiConstants.Yaml => new OpenApiYamlWriter(textWriter, settings),
_ => throw new ArgumentException("Unknown format"),
};
diff --git a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj
index cfd669367..2303fdedc 100644
--- a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj
+++ b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj
@@ -13,7 +13,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs
index 1ed0aacab..fdca97797 100755
--- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs
+++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs
@@ -88,7 +88,8 @@ public static Task SerializeAsync(
IOpenApiWriter writer = format.ToLowerInvariant() switch
{
- OpenApiConstants.Json => new OpenApiJsonWriter(streamWriter, settings, false),
+ OpenApiConstants.Json when settings is OpenApiJsonWriterSettings jsonSettings => new OpenApiJsonWriter(streamWriter, jsonSettings),
+ OpenApiConstants.Json => new OpenApiJsonWriter(streamWriter, settings),
OpenApiConstants.Yaml => new OpenApiYamlWriter(streamWriter, settings),
_ => throw new OpenApiException(string.Format(SRResource.OpenApiFormatNotSupported, format)),
};
diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs
index feffb3b59..11dda6c59 100644
--- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs
+++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
+using System;
+using System.ComponentModel;
using System.IO;
namespace Microsoft.OpenApi
@@ -14,7 +16,17 @@ public class OpenApiJsonWriter : OpenApiWriterBase
/// Initializes a new instance of the class.
///
/// The text writer.
- public OpenApiJsonWriter(TextWriter textWriter) : base(textWriter, null)
+ public OpenApiJsonWriter(TextWriter textWriter) : this(textWriter, (OpenApiWriterSettings?)null)
+ {
+ // this constructor is kept for binary compatibility
+ // TODO remove in next major version and make the settings an optional parameter in the other constructor
+ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Settings for controlling how the OpenAPI document will be written out.
+ /// The text writer.
+ public OpenApiJsonWriter(TextWriter textWriter, OpenApiWriterSettings? settings) : base(textWriter, settings ?? new OpenApiJsonWriterSettings())
{
}
@@ -34,9 +46,13 @@ public OpenApiJsonWriter(TextWriter textWriter, OpenApiJsonWriterSettings settin
/// The text writer.
/// Settings for controlling how the OpenAPI document will be written out.
/// Setting for allowing the JSON emitted to be in terse format.
- public OpenApiJsonWriter(TextWriter textWriter, OpenApiWriterSettings? settings, bool terseOutput = false) : base(textWriter, settings)
+ [Obsolete("Use OpenApiJsonWriter(TextWriter textWriter, OpenApiJsonWriterSettings settings) instead.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public OpenApiJsonWriter(TextWriter textWriter, OpenApiWriterSettings? settings, bool terseOutput) : base(textWriter, settings)
{
_produceTerseOutput = terseOutput;
+ // this constructor is kept for binary compatibility, terse information should be read from the settings to avoid fork APIs.
+ // TODO remove in next major version
}
///
diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
index 7d80480d0..e3848ec69 100644
--- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
+++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
@@ -55,10 +55,7 @@ protected OpenApiWriterBase(TextWriter textWriter, OpenApiWriterSettings? settin
Writer.NewLine = "\n";
Scopes = new();
- if (settings == null)
- {
- settings = new();
- }
+ settings ??= new();
Settings = settings;
}
diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs
index f2e4aa5a9..f346dddc4 100644
--- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs
+++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs
@@ -141,9 +141,9 @@ public static void WriteOptionalObject(
{
if (value != null)
{
- if (value is IEnumerable values && !values.GetEnumerator().MoveNext())
+ if (value is IEnumerable values && value is not JsonArray && !values.GetEnumerator().MoveNext())
{
- return; // Don't render optional empty collections
+ return; // Don't render optional empty collections except for the Default properties which are JsonArray
}
writer.WriteRequiredObject(name, value, action);
diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj
index 33656177d..5b5b5fedf 100644
--- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
index 8e19f7370..39befa33f 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
+++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
@@ -19,9 +19,9 @@
-
+
-
+
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs
index 72fb153dd..c992f6656 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs
@@ -315,6 +315,40 @@ public void CloningSchemaWithExamplesAndEnumsShouldSucceed()
Assert.Equivalent(6, clone.Default.GetValue());
}
+ [Fact]
+ public void DefaultEmptyCollectionShouldRoundTrip()
+ {
+ // Given
+ var serializedSchema =
+ """
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": []
+ }
+ }
+ """;
+ using var textWriter = new StringWriter();
+ var writer = new OpenApiJsonWriter(textWriter);
+
+ // When
+ var schema = OpenApiModelFactory.Parse(serializedSchema, OpenApiSpecVersion.OpenApi3_1, new(), out _, "json", SettingsFixture.ReaderSettings);
+
+ var deserializedArray = Assert.IsType(schema.Items.Default);
+ Assert.Empty(deserializedArray);
+
+ schema.SerializeAsV31(writer);
+ var roundTrippedSchema = textWriter.ToString();
+
+ // Then
+ var parsedResult = JsonNode.Parse(roundTrippedSchema);
+ var parsedExpected = JsonNode.Parse(serializedSchema);
+ Assert.True(JsonNode.DeepEquals(parsedExpected, parsedResult));
+ var resultingArray = Assert.IsType(parsedResult["items"]?["default"]);
+ Assert.Empty(resultingArray);
+ }
+
[Fact]
public async Task SerializeV31SchemaWithMultipleTypesAsV3Works()
{
diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs
new file mode 100644
index 000000000..a1e323ebf
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs
@@ -0,0 +1,93 @@
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.OpenApi.Tests.Extensions;
+
+public class OpenApiSerializableExtensionsTests
+{
+ [Fact]
+ public async Task UsesTheTerseOutputInformationFromSettingsTrue()
+ {
+ var parameter = new OpenApiParameter
+ {
+ Name = "param1",
+ In = ParameterLocation.Query,
+ Description = "A sample parameter",
+ Required = false,
+ Schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.String
+ }
+ };
+
+ var settings = new OpenApiJsonWriterSettings
+ {
+ Terse = true
+ };
+
+ using var stream = new MemoryStream();
+ await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings);
+
+ stream.Position = 0;
+ using var reader = new StreamReader(stream);
+ var output = await reader.ReadToEndAsync();
+
+ Assert.Equal("{\"name\":\"param1\",\"in\":\"query\",\"description\":\"A sample parameter\",\"schema\":{\"type\":\"string\"}}", output);
+ }
+
+ [Fact]
+ public async Task UsesTheTerseOutputInformationFromSettingsFalse()
+ {
+ var parameter = new OpenApiParameter
+ {
+ Name = "param1",
+ In = ParameterLocation.Query,
+ Description = "A sample parameter",
+ Required = false,
+ Schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.String
+ }
+ };
+
+ var settings = new OpenApiJsonWriterSettings
+ {
+ Terse = false
+ };
+
+ using var stream = new MemoryStream();
+ await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings);
+
+ stream.Position = 0;
+ using var reader = new StreamReader(stream);
+ var output = await reader.ReadToEndAsync();
+
+ Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"description\": \"A sample parameter\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output);
+ }
+
+ [Fact]
+ public async Task UsesTheTerseOutputInformationFromSettingsNoSettings()
+ {
+ var parameter = new OpenApiParameter
+ {
+ Name = "param1",
+ In = ParameterLocation.Query,
+ Description = "A sample parameter",
+ Required = false,
+ Schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.String
+ }
+ };
+
+ using var stream = new MemoryStream();
+ await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, null);
+
+ stream.Position = 0;
+ using var reader = new StreamReader(stream);
+ var output = await reader.ReadToEndAsync();
+
+ Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"description\": \"A sample parameter\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output);
+ }
+}
diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
index 763db67d6..98b459aa6 100644
--- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
@@ -13,12 +13,12 @@
-
-
+
+
-
+
-
+
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 408ddb15e..87276efce 100644
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -795,7 +795,10 @@ namespace Microsoft.OpenApi
{
public OpenApiJsonWriter(System.IO.TextWriter textWriter) { }
public OpenApiJsonWriter(System.IO.TextWriter textWriter, Microsoft.OpenApi.OpenApiJsonWriterSettings settings) { }
- public OpenApiJsonWriter(System.IO.TextWriter textWriter, Microsoft.OpenApi.OpenApiWriterSettings? settings, bool terseOutput = false) { }
+ public OpenApiJsonWriter(System.IO.TextWriter textWriter, Microsoft.OpenApi.OpenApiWriterSettings? settings) { }
+ [System.Obsolete("Use OpenApiJsonWriter(TextWriter textWriter, OpenApiJsonWriterSettings settings) " +
+ "instead.")]
+ public OpenApiJsonWriter(System.IO.TextWriter textWriter, Microsoft.OpenApi.OpenApiWriterSettings? settings, bool terseOutput) { }
protected override int BaseIndentation { get; }
public override void WriteEndArray() { }
public override void WriteEndObject() { }