diff --git a/src/Microsoft.OpenApi/Extensions/DictionaryExtensions.cs b/src/Microsoft.OpenApi/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000..30e5aea3a --- /dev/null +++ b/src/Microsoft.OpenApi/Extensions/DictionaryExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OpenApi.Extensions +{ + /// + /// Dictionary extension methods. + /// + public static class DictionaryExtensions + { + /// + /// Returns a new dictionary with entries sorted by key using the default comparer. + /// + public static SortedDictionary Sort( + this Dictionary source) + where TKey : notnull + { + Utils.CheckArgumentNull(source); + + return new SortedDictionary(source); + } + + /// + /// Returns a new dictionary with entries sorted by key using a custom comparer. + /// + public static SortedDictionary Sort( + this Dictionary source, + IComparer comparer) + where TKey : notnull + { + Utils.CheckArgumentNull(source); + Utils.CheckArgumentNull(comparer); + + return new SortedDictionary(source, comparer); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Extensions/DictionaryExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/DictionaryExtensionsTests.cs new file mode 100644 index 000000000..e00dba156 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Extensions/DictionaryExtensionsTests.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Extensions; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Extensions +{ + public class DictionaryExtensionsTests + { + [Fact] + public void ShouldSortStringIntDictionaryInAscendingOrder() + { + var dict = new Dictionary { { "b", 2 }, { "a", 1 } }; + var result = dict.Sort(); + Assert.Equal(["a", "b"], result.Keys); + } + + [Fact] + public void ShouldReturnEmptyDictionaryWhenSourceIsEmpty() + { + var dict = new Dictionary(); + var result = dict.Sort(); + Assert.Empty(result); + } + + [Fact] + public void ShouldKeepOrderWhenDictionaryIsAlreadySorted() + { + var dict = new Dictionary { { "a", 1 }, { "b", 2 } }; + var result = dict.Sort(); + Assert.Equal(["a", "b"], result.Keys); + } + + [Fact] + public void ShouldSortNumericKeysNaturally() + { + var dict = new Dictionary { { 10, "a" }, { 1, "b" } }; + var result = dict.Sort(); + Assert.Equal([1, 10], result.Keys); + } + + [Fact] + public void ShouldSortDateTimeKeysInAscendingOrder() + { + var now = DateTime.Now; + var later = now.AddHours(1); + + var dict = new Dictionary + { + [later] = "future", + [now] = "present" + }; + + var result = dict.Sort(); + Assert.Equal([now, later], result.Keys); + } + + [Fact] + public void ShouldSortWithCustomDescendingComparer() + { + var dict = new Dictionary { { "a", 1 }, { "b", 2 } }; + var result = dict.Sort(Comparer.Create((x, y) => y.CompareTo(x))); + Assert.Equal(["b", "a"], result.Keys); + } + + [Fact] + public void ShouldSortDictionaryWithComplexValueTypes() + { + var dict = new Dictionary> + { + { "z", new HashSet { "value1" } }, + { "a", new HashSet { "value2" } } + }; + + var result = dict.Sort(); + Assert.Equal(["a", "z"], result.Keys); + Assert.Equal(new HashSet { "value2" }, result["a"]); + } + + [Fact] + public void ShouldSortDictionaryWithNullValues() + { + var dict = new Dictionary + { + { "b", null }, + { "a", "value" } + }; + + var result = dict.Sort(); + Assert.Equal(["a", "b"], result.Keys); + Assert.Null(result["b"]); + } + + [Fact] + public void ShouldSortDictionaryOfDictionariesByOuterKey() + { + var dict = new Dictionary> + { + ["z"] = new Dictionary { { "x", "1" } }, + ["a"] = new Dictionary { { "y", "2" } } + }; + + var result = dict.Sort(); + Assert.Equal(["a", "z"], result.Keys); + Assert.Equal("2", result["a"]["y"]); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 755a9e17e..38d9b4c59 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -142,6 +142,13 @@ namespace Microsoft.OpenApi.Expressions } namespace Microsoft.OpenApi.Extensions { + public static class DictionaryExtensions + { + public static System.Collections.Generic.SortedDictionary Sort(this System.Collections.Generic.Dictionary source) + where TKey : notnull { } + public static System.Collections.Generic.SortedDictionary Sort(this System.Collections.Generic.Dictionary source, System.Collections.Generic.IComparer comparer) + where TKey : notnull { } + } public static class EnumExtensions { [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2075", Justification="Fields are never trimmed for enum types.")]