From f52fda47eabdfe0d8f6da92e5bade3a382b774f9 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 31 Jul 2025 16:19:15 +0800 Subject: [PATCH 1/3] support jsonc --- .../JsonKeyValueAdapter.cs | 8 +- .../Unit/JsonContentTypeTests.cs | 84 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs index 74d2b882a..b5274fc00 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs @@ -4,6 +4,7 @@ using Azure.Data.AppConfiguration; using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions; using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Mime; @@ -15,6 +16,11 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration { internal class JsonKeyValueAdapter : IKeyValueAdapter { + private JsonDocumentOptions _jsonParseOptions = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip + }; + public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { if (setting == null) @@ -28,7 +34,7 @@ public Task>> ProcessKeyValue(Configura try { - using (JsonDocument document = JsonDocument.Parse(rootJson)) + using (JsonDocument document = JsonDocument.Parse(rootJson, _jsonParseOptions)) { keyValuePairs = new JsonFlattener().FlattenJson(document.RootElement); } diff --git a/tests/Tests.AzureAppConfiguration/Unit/JsonContentTypeTests.cs b/tests/Tests.AzureAppConfiguration/Unit/JsonContentTypeTests.cs index 91cb03a87..eaca360ff 100644 --- a/tests/Tests.AzureAppConfiguration/Unit/JsonContentTypeTests.cs +++ b/tests/Tests.AzureAppConfiguration/Unit/JsonContentTypeTests.cs @@ -276,6 +276,90 @@ public void JsonContentTypeTests_JsonKeyValueAdapterCannotProcessKeyVaultReferen Assert.False(jsonKeyValueAdapter.CanProcess(setting)); } + [Fact] + public void JsonContentTypeTests_LoadJsonValuesWithComments() + { + List _kvCollection = new List + { + // Test various comment styles and positions + ConfigurationModelFactory.ConfigurationSetting( + key: "MixedCommentStyles", + value: @"{ + // Single line comment at start + ""ApiSettings"": { + ""BaseUrl"": ""https://api.example.com"", // Inline single line + /* Multi-line comment + spanning multiple lines */ + ""ApiKey"": ""secret-key"", + ""Endpoints"": [ + // Comment before array element + ""/users"", + /* Comment between elements */ + ""/orders"", + ""/products"" // Comment after element + ] + }, + // Test edge cases + ""StringWithSlashes"": ""This is not a // comment"", + ""StringWithStars"": ""This is not a /* comment */"", + ""UrlValue"": ""https://example.com/path"", // This is a real comment + ""EmptyComment"": ""value"", // + /**/ + ""AfterEmptyComment"": ""value2"" + /* Final multi-line comment */ + }", + contentType: "application/json"), + // Test invalid JSON with comments + ConfigurationModelFactory.ConfigurationSetting( + key: "InvalidJsonWithComments", + value: @"// This is a comment + { invalid json structure + // Another comment + missing quotes and braces", + contentType: "application/json"), + // Test only comments (should be invalid JSON) + ConfigurationModelFactory.ConfigurationSetting( + key: "OnlyComments", + value: @" + // Just comments + /* No actual content */ + ", + contentType: "application/json") + }; + + var mockClientManager = GetMockConfigurationClientManager(_kvCollection); + + var config = new ConfigurationBuilder() + .AddAzureAppConfiguration(options => options.ClientManager = mockClientManager) + .Build(); + + // Verify mixed comment styles are properly parsed + Assert.Equal("https://api.example.com", config["MixedCommentStyles:ApiSettings:BaseUrl"]); + Assert.Equal("secret-key", config["MixedCommentStyles:ApiSettings:ApiKey"]); + Assert.Equal("/users", config["MixedCommentStyles:ApiSettings:Endpoints:0"]); + Assert.Equal("/orders", config["MixedCommentStyles:ApiSettings:Endpoints:1"]); + Assert.Equal("/products", config["MixedCommentStyles:ApiSettings:Endpoints:2"]); + + // Verify edge cases where comment-like text appears in strings + Assert.Equal("This is not a // comment", config["MixedCommentStyles:StringWithSlashes"]); + Assert.Equal("This is not a /* comment */", config["MixedCommentStyles:StringWithStars"]); + Assert.Equal("https://example.com/path", config["MixedCommentStyles:UrlValue"]); + Assert.Equal("value", config["MixedCommentStyles:EmptyComment"]); + Assert.Equal("value2", config["MixedCommentStyles:AfterEmptyComment"]); + + // Invalid JSON should fall back to string value + Assert.Equal(@"// This is a comment + { invalid json structure + // Another comment + missing quotes and braces", config["InvalidJsonWithComments"]); + + // Only comments should be treated as string value (invalid JSON) + Assert.Equal(@" + // Just comments + /* No actual content */ + ", config["OnlyComments"]); + } + private IConfigurationClientManager GetMockConfigurationClientManager(List _kvCollection) { var mockResponse = new Mock(); From 04f6ce75ddfb9f7098dfc995a3f3412aa0c5be56 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 31 Jul 2025 16:21:47 +0800 Subject: [PATCH 2/3] remove unused reference --- .../JsonKeyValueAdapter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs index b5274fc00..77ab4c534 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs @@ -4,7 +4,6 @@ using Azure.Data.AppConfiguration; using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions; using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement; -using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Mime; From 5ab8381de5a997036ac1134bf6a265a895cd6242 Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Tue, 5 Aug 2025 13:35:21 +0800 Subject: [PATCH 3/3] use private static option --- .../JsonKeyValueAdapter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs index 77ab4c534..d353439fd 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/JsonKeyValueAdapter.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration { internal class JsonKeyValueAdapter : IKeyValueAdapter { - private JsonDocumentOptions _jsonParseOptions = new JsonDocumentOptions + private static readonly JsonDocumentOptions JsonParseOptions = new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }; @@ -33,7 +33,7 @@ public Task>> ProcessKeyValue(Configura try { - using (JsonDocument document = JsonDocument.Parse(rootJson, _jsonParseOptions)) + using (JsonDocument document = JsonDocument.Parse(rootJson, JsonParseOptions)) { keyValuePairs = new JsonFlattener().FlattenJson(document.RootElement); }