diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs index 5b1c69ac..89a6806d 100644 --- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs +++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs @@ -2,7 +2,6 @@ using System.Linq.Dynamic.Core.NewtonsoftJson.Config; using System.Linq.Dynamic.Core.NewtonsoftJson.Extensions; using System.Linq.Dynamic.Core.Validation; -using System.Linq.Expressions; using Newtonsoft.Json.Linq; namespace System.Linq.Dynamic.Core.NewtonsoftJson; @@ -550,7 +549,7 @@ public static JArray Select(this JArray source, NewtonsoftJsonParsingConfig conf if (source.Count == 0) { - return new JArray(); + return []; } var queryable = ToQueryable(source, config); @@ -591,6 +590,43 @@ public static JArray Select(this JArray source, Type resultType, string selector } #endregion Select + #region SelectMany + /// + /// Projects each element of a sequence to an and combines the resulting sequences into one sequence. + /// + /// A sequence of values to project. + /// A projection string expression to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// A whose elements are the result of invoking a one-to-many projection function on each element of the input sequence. + public static JArray SelectMany(this JArray source, string selector, params object?[] args) + { + return SelectMany(source, NewtonsoftJsonParsingConfig.Default, selector, args); + } + + /// + /// Projects each element of a sequence to an and combines the resulting sequences into one sequence. + /// + /// A sequence of values to project. + /// The . + /// A projection string expression to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// A whose elements are the result of invoking a one-to-many projection function on each element of the input sequence. + public static JArray SelectMany(this JArray source, NewtonsoftJsonParsingConfig config, string selector, params object?[] args) + { + Check.NotNull(source); + Check.NotNull(config); + Check.NotNullOrEmpty(selector); + + if (source.Count == 0) + { + return []; + } + + var queryable = ToQueryable(source, config); + return ToJArray(() => queryable.SelectMany(config, selector, args)); + } + #endregion SelectMany + #region Single /// /// Returns the only element of a sequence that satisfies a specified condition, and throws an exception if there diff --git a/src/System.Linq.Dynamic.Core.SystemTextJson/SystemTextJsonExtensions.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/SystemTextJsonExtensions.cs index 48ccc581..a14a468e 100644 --- a/src/System.Linq.Dynamic.Core.SystemTextJson/SystemTextJsonExtensions.cs +++ b/src/System.Linq.Dynamic.Core.SystemTextJson/SystemTextJsonExtensions.cs @@ -4,7 +4,6 @@ using System.Linq.Dynamic.Core.SystemTextJson.Extensions; using System.Linq.Dynamic.Core.SystemTextJson.Utils; using System.Linq.Dynamic.Core.Validation; -using System.Linq.Expressions; using System.Text.Json; namespace System.Linq.Dynamic.Core.SystemTextJson; @@ -676,7 +675,7 @@ public static JsonDocument Select(this JsonDocument source, string selector, par /// The . /// A projection string expression to apply to each element. /// An object array that contains zero or more objects to insert into the predicate as parameters. - /// An whose elements are the result of invoking a projection string on each element of source. + /// An whose elements are the result of invoking a projection string on each element of source. public static JsonDocument Select(this JsonDocument source, SystemTextJsonParsingConfig config, string selector, params object?[] args) { Check.NotNull(source); @@ -721,6 +720,38 @@ public static JsonDocument Select(this JsonDocument source, Type resultType, str } #endregion Select + #region SelectMany + /// + /// Projects each element of a sequence to an and combines the resulting sequences into one sequence. + /// + /// The source + /// A projection string expression to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. + /// A whose elements are the result of invoking a one-to-many projection function on each element of the input sequence. + public static JsonDocument SelectMany(this JsonDocument source, string selector, params object?[] args) + { + return SelectMany(source, SystemTextJsonParsingConfig.Default, selector, args); + } + + /// + /// Projects each element of a sequence to an and combines the resulting sequences into one sequence. + /// + /// The source + /// The . + /// A projection string expression to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. + /// A whose elements are the result of invoking a one-to-many projection function on each element of the input sequence. + public static JsonDocument SelectMany(this JsonDocument source, SystemTextJsonParsingConfig config, string selector, params object?[] args) + { + Check.NotNull(source); + Check.NotNull(config); + Check.NotNullOrEmpty(selector); + + var queryable = ToQueryable(source, config); + return ToJsonDocumentArray(() => queryable.SelectMany(config, selector, args)); + } + #endregion SelectMany + #region Single /// /// Returns the only element of a sequence, and throws an exception if there diff --git a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs index debfb4b7..dafa0373 100644 --- a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs +++ b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs @@ -323,6 +323,37 @@ public void Select_ResultType() array.Should().ContainInOrder(1, 4, 9); } + [Fact] + public void SelectMany() + { + // Arrange + var json = + """ + [{ + "PhoneNumbers": [ + { "Number": "123" }, + { "Number": "456" } + ] + }, + { + "PhoneNumbers": [ + { "Number": "789" }, + { "Number": "012" } + ] + }] + """; + var source = JArray.Parse(json); + + // Act + var result = source + .SelectMany("PhoneNumbers") + .Select("Number"); + + // Assert + var array = result.Select(x => x.Value()); + array.Should().BeEquivalentTo("123", "456", "789", "012"); + } + [Fact] public void Single() { diff --git a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs index ca337755..7041ad7c 100644 --- a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs +++ b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs @@ -320,6 +320,37 @@ public void Select() array.Should().BeEquivalentTo("John", "Doe"); } + [Fact] + public void SelectMany() + { + // Arrange + var json = + """ + [{ + "PhoneNumbers": [ + { "Number": "123" }, + { "Number": "456" } + ] + }, + { + "PhoneNumbers": [ + { "Number": "789" }, + { "Number": "012" } + ] + }] + """; + var source = JsonDocument.Parse(json); + + // Act + var result = source + .SelectMany("PhoneNumbers") + .Select("Number"); + + // Assert + var array = result.RootElement.EnumerateArray().Select(x => x.GetString()); + array.Should().BeEquivalentTo("123", "456", "789", "012"); + } + [Fact] public void Single() {