diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs index 354b94c6e2bbd1..4029c922504fdf 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs @@ -43,13 +43,22 @@ private IDictionary ParseStream(Stream input) return _data; } - private void VisitElement(JsonElement element) { + private void VisitElement(JsonElement element) + { + var isEmpty = true; + foreach (JsonProperty property in element.EnumerateObject()) { + isEmpty = false; EnterContext(property.Name); VisitValue(property.Value); ExitContext(); } + + if (isEmpty) + { + _data[_currentPath] = null; + } } private void VisitValue(JsonElement value) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs new file mode 100644 index 00000000000000..5f2f085717a0e4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Configuration.Test; +using Xunit; + +namespace Microsoft.Extensions.Configuration.Json.Test +{ + public class EmptyObjectTest + { + [Fact] + public void EmptyObject_AddsAsNull() + { + var json = @"{ + ""key"": { }, + }"; + + var jsonConfigSource = new JsonConfigurationProvider(new JsonConfigurationSource()); + jsonConfigSource.Load(TestStreamHelpers.StringToStream(json)); + + Assert.Null(jsonConfigSource.Get("key")); + } + + [Fact] + public void NullObject_AddsEmptyString() + { + var json = @"{ + ""key"": null, + }"; + + var jsonConfigSource = new JsonConfigurationProvider(new JsonConfigurationSource()); + jsonConfigSource.Load(TestStreamHelpers.StringToStream(json)); + + Assert.Equal("", jsonConfigSource.Get("key")); + } + + [Fact] + public void NestedObject_DoesNotAddParent() + { + var json = @"{ + ""key"": { + ""nested"": ""value"" + }, + }"; + + var jsonConfigSource = new JsonConfigurationProvider(new JsonConfigurationSource()); + jsonConfigSource.Load(TestStreamHelpers.StringToStream(json)); + + Assert.False(jsonConfigSource.TryGet("key", out _)); + Assert.Equal("value", jsonConfigSource.Get("key:nested")); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs new file mode 100644 index 00000000000000..b206e3180033a2 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration.Test; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.Extensions.Configuration.Json.Test +{ + public class IntegrationTest + { + [Fact] + public void LoadJsonConfiguration() + { + var json = @"{ + ""a"": ""b"", + ""c"": { + ""d"": ""e"" + }, + ""f"": """", + ""g"": null, + ""h"": {}, + ""i"": { + ""k"": {} + } + }"; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddJsonStream(TestStreamHelpers.StringToStream(json)); + var configuration = configurationBuilder.Build(); + + Assert.Collection(configuration.GetChildren(), + new Action[] { + x => AssertSection(x, "a", "b"), + x => AssertSection(x, "c", null, new Action[] { + x => AssertSection(x, "d", "e"), + }), + x => AssertSection(x, "f", ""), + x => AssertSection(x, "g", ""), + x => AssertSection(x, "h", null), + x => AssertSection(x, "i", null, new Action[] { + x => AssertSection(x, "k", null), + }), + }); + } + + private static void AssertSection(IConfigurationSection configurationSection, string key, string value) + => AssertSection(configurationSection, key, value, new Action[0]); + + private static void AssertSection(IConfigurationSection configurationSection, string key, string value, Action[] childrenInspectors) + { + if (key != configurationSection.Key || value != configurationSection.Value) + { + throw new EqualException( + expected: GetString(key, value), + actual: GetString(configurationSection)); + } + + Assert.Collection(configurationSection.GetChildren(), childrenInspectors); + } + + private static string GetString(IConfigurationSection configurationSection) => GetString(configurationSection.Key, configurationSection.Value); + private static string GetString(string key, string value) => $"\"{key}\":" + (value is null ? "null" : $"\"{value}\""); + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationTest.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationTest.cs index 45bafb48853ccb..40f2b23a9051fc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationTest.cs @@ -769,6 +769,69 @@ public void SectionWithChildrenExists() Assert.False(sectionNotExists); } + [Theory] + [InlineData("Value1")] + [InlineData("")] + public void KeyWithValueAndWithoutChildrenExistsAsSection(string value) + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", value} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dict); + var config = configurationBuilder.Build(); + + // Act + var sectionExists = config.GetSection("Mem1").Exists(); + + // Assert + Assert.True(sectionExists); + } + + [Fact] + public void KeyWithNullValueAndWithoutChildrenIsASectionButNotExists() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1", null} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dict); + var config = configurationBuilder.Build(); + + // Act + var sections = config.GetChildren(); + var sectionExists = config.GetSection("Mem1").Exists(); + var sectionChildren = config.GetSection("Mem1").GetChildren(); + + // Assert + Assert.Single(sections, section => section.Key == "Mem1"); + Assert.False(sectionExists); + Assert.Empty(sectionChildren); + } + + [Fact] + public void SectionWithChildrenHasNullValue() + { + // Arrange + var dict = new Dictionary() + { + {"Mem1:KeyInMem1", "ValueInMem1"}, + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dict); + var config = configurationBuilder.Build(); + + // Act + var sectionValue = config.GetSection("Mem1").Value; + + // Assert + Assert.Null(sectionValue); + } + [Fact] public void NullSectionDoesNotExist() {