From d27141b5203e5895f0fb812b913dd7621274e6a3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 09:32:14 -0400 Subject: [PATCH 1/5] fix: serialize examples as extension in v2/v3 Signed-off-by: Vincent Biret --- .../Models/OpenApiConstants.cs | 5 ++ src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 3 ++ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 1 + .../Models/OpenApiSchemaTests.cs | 54 +++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index db8aa1d62..3cfc9f357 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -755,6 +755,11 @@ public static class OpenApiConstants /// public const string ExamplesExtension = "x-examples"; + /// + /// Extension: x-jsonschema-examples + /// + public const string JsonSchemaExamplesExtension = "x-jsonschema-examples"; + /// /// Field: version3_0_0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 814dc1923..346b3a738 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -741,6 +741,7 @@ private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action nodeWriter.WriteAny(s)); } internal void WriteAsItemsProperties(IOpenApiWriter writer) @@ -985,6 +986,8 @@ private void SerializeAsV2( writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w)); } + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); + // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index e763311ca..524d86c60 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,2 +1,3 @@ #nullable enable const Microsoft.OpenApi.OpenApiConstants.OaiLicenseIdentifier = "x-oai-license-identifier" -> string! +const Microsoft.OpenApi.OpenApiConstants.JsonSchemaExamplesExtension = "x-jsonschema-examples" -> string! diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 14ec467aa..a8b5c24d5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1746,6 +1746,60 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + public async Task SerializeExamplesAsExtensionInEarlierVersions(OpenApiSpecVersion version) + { + var expected = """ + { + "x-jsonschema-examples": [ + "example value", + 42 + ] + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")!, + JsonValue.Create(42)! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecVersion version) + { + var expected = """ + { + "examples": [ + "example value", + 42 + ] + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")!, + JsonValue.Create(42)! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] From be57a7c4cf1e4ae2025b987520316420c897cb46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:56:05 +0000 Subject: [PATCH 2/5] fix(schema): serialize compatibility examples from examples list --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 31 ++++++++++++++++--- .../Models/OpenApiSchemaTests.cs | 27 ++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 346b3a738..b94ab74eb 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -615,7 +615,10 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, callback); // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + writer.WriteOptionalObject( + OpenApiConstants.Example, + version < OpenApiSpecVersion.OpenApi3_1 ? GetCompatibilityExample() : Example, + (w, e) => w.WriteAny(e)); // deprecated writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); @@ -741,7 +744,7 @@ private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action nodeWriter.WriteAny(s)); + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s)); } internal void WriteAsItemsProperties(IOpenApiWriter writer) @@ -955,7 +958,7 @@ private void SerializeAsV2( writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); // example - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e)); // x-nullable extension SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0); @@ -986,7 +989,7 @@ private void SerializeAsV2( writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, (w, s) => s.SerializeAsV2(w)); } - writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); + writer.WriteOptionalCollection(OpenApiConstants.JsonSchemaExamplesExtension, GetCompatibilityExamplesExtension(), (nodeWriter, s) => nodeWriter.WriteAny(s)); // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); @@ -1022,6 +1025,26 @@ private bool TrySerializeTypeProperty(IOpenApiWriter writer, OpenApiSpecVersion return false; } + private JsonNode? GetCompatibilityExample() + { + return Example ?? Examples?.FirstOrDefault(); + } + + private IEnumerable? GetCompatibilityExamplesExtension() + { + if (Examples is null || Examples.Count == 0) + { + return null; + } + + if (Example is not null) + { + return Examples; + } + + return Examples.Count > 1 ? Examples.Skip(1) : null; + } + private static bool IsPowerOfTwo(int x) { return x != 0 && (x & (x - 1)) == 0; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index a8b5c24d5..1dc698691 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1749,12 +1749,35 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeExamplesAsExtensionInEarlierVersions(OpenApiSpecVersion version) + public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) { var expected = """ { + "example": "example value" + } + """; + var schema = new OpenApiSchema + { + Examples = + [ + JsonValue.Create("example value")! + ] + }; + + var actual = await schema.SerializeAsJsonAsync(version); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + public async Task SerializeMultipleExamplesAsExampleAndExtensionInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + { + var expected = """ + { + "example": "example value", "x-jsonschema-examples": [ - "example value", 42 ] } From b5af19c0acc3ce2798beb80f9df7f55191072fc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:00:31 +0000 Subject: [PATCH 3/5] test(schema): shorten compatibility example test names --- test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 1dc698691..5fed0cf4a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1749,7 +1749,7 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + public async Task SerializeSingleExampleAsExampleInV2V3WhenExampleUnset(OpenApiSpecVersion version) { var expected = """ { @@ -1772,7 +1772,7 @@ public async Task SerializeSingleExampleAsExamplePropertyInEarlierVersionsWhenEx [Theory] [InlineData(OpenApiSpecVersion.OpenApi2_0)] [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeMultipleExamplesAsExampleAndExtensionInEarlierVersionsWhenExampleIsUnset(OpenApiSpecVersion version) + public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVersion version) { var expected = """ { From 095ae3bbdf3c248ba18db17278889cf5a8faf01c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 13:06:40 -0400 Subject: [PATCH 4/5] feat: adds deserialization of the example extension Signed-off-by: Vincent Biret --- .../Reader/V2/OpenApiSchemaDeserializer.cs | 4 ++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 4 ++ .../Models/OpenApiSchemaTests.cs | 70 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs index af7f33d9c..25d36eff1 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs @@ -267,6 +267,10 @@ internal static partial class OpenApiV2Deserializer "example", (o, n, _, _) => o.Example = n }, + { + OpenApiConstants.JsonSchemaExamplesExtension, + (o, n, _, c) => o.Examples = n.CreateListOfAny(c) + }, { OpenApiConstants.PatternPropertiesExtension, (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 48dde9d5e..9b85fcf51 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -280,6 +280,10 @@ internal static partial class OpenApiV3Deserializer "example", (o, n, _, _) => o.Example = n }, + { + OpenApiConstants.JsonSchemaExamplesExtension, + (o, n, _, c) => o.Examples = n.CreateListOfAny(c) + }, { "deprecated", (o, n, _, _) => diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 5fed0cf4a..51f7fc5a0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1914,6 +1914,76 @@ public void DeserializePatternPropertiesExtensionInV3AssignsPatternPropertiesPro Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey("x-jsonschema-patternProperties")); } + [Fact] + public void DeserializeExamplesExtensionInV2AssignsExamplesProperty() + { + var jsonContent = """ + { + "swagger": "2.0", + "info": { "title": "Test", "version": "1.0" }, + "paths": {}, + "definitions": { + "TestSchema": { + "type": "string", + "example": "primary example", + "x-jsonschema-examples": [ + "secondary example", + 42 + ] + } + } + } + """; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schema = readResult.Document.Components.Schemas["TestSchema"]; + Assert.Equal("primary example", schema.Example?.GetValue()); + Assert.NotNull(schema.Examples); + Assert.Collection( + schema.Examples, + example => Assert.Equal("secondary example", example.GetValue()), + example => Assert.Equal(42, example.GetValue())); + Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension)); + } + + [Fact] + public void DeserializeExamplesExtensionInV3AssignsExamplesProperty() + { + var jsonContent = """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "1.0" }, + "paths": {}, + "components": { + "schemas": { + "TestSchema": { + "type": "string", + "example": "primary example", + "x-jsonschema-examples": [ + "secondary example", + 42 + ] + } + } + } + } + """; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schema = readResult.Document.Components.Schemas["TestSchema"]; + Assert.Equal("primary example", schema.Example?.GetValue()); + Assert.NotNull(schema.Examples); + Assert.Collection( + schema.Examples, + example => Assert.Equal("secondary example", example.GetValue()), + example => Assert.Equal(42, example.GetValue())); + Assert.True(schema.Extensions is null || !schema.Extensions.ContainsKey(OpenApiConstants.JsonSchemaExamplesExtension)); + } + [Fact] public void DeserializeContainsExtensionsInV3AssignsContainsProperties() { From dc24f5447f505d7df6afab6fadeebbb547969363 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Jul 2026 14:52:08 -0400 Subject: [PATCH 5/5] chore: removes future version after cherry-pick --- test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 51f7fc5a0..1b2a313d5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1798,7 +1798,6 @@ public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVer [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_1)] - [InlineData(OpenApiSpecVersion.OpenApi3_2)] public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecVersion version) { var expected = """