Skip to content

Commit 3208964

Browse files
authored
Add GroupBy method for Z.DynamicLinq.SystemTextJson and Z.DynamicLinq.NewtonsoftJson (#929)
* Add GroupBy method for Z.DynamicLinq.SystemTextJson and Z.DynamicLinq.NewtonsoftJson * .
1 parent 186ac52 commit 3208964

File tree

4 files changed

+271
-7
lines changed

4 files changed

+271
-7
lines changed

src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Linq.Dynamic.Core.NewtonsoftJson.Config;
33
using System.Linq.Dynamic.Core.NewtonsoftJson.Extensions;
44
using System.Linq.Dynamic.Core.Validation;
5+
using JetBrains.Annotations;
56
using Newtonsoft.Json.Linq;
67

78
namespace System.Linq.Dynamic.Core.NewtonsoftJson;
@@ -303,6 +304,42 @@ public static JToken First(this JArray source, string predicate, params object?[
303304
}
304305
#endregion FirstOrDefault
305306

307+
#region GroupBy
308+
/// <summary>
309+
/// Groups the elements of a sequence according to a specified key string function
310+
/// and creates a result value from each group and its key.
311+
/// </summary>
312+
/// <param name="source">A <see cref="JArray"/> whose elements to group.</param>
313+
/// <param name="keySelector">A string expression to specify the key for each element.</param>
314+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
315+
/// <returns>A <see cref="JArray"/> where each element represents a projection over a group and its key.</returns>
316+
[PublicAPI]
317+
public static JArray GroupBy(this JArray source, string keySelector, params object[]? args)
318+
{
319+
return GroupBy(source, NewtonsoftJsonParsingConfig.Default, keySelector, args);
320+
}
321+
322+
/// <summary>
323+
/// Groups the elements of a sequence according to a specified key string function
324+
/// and creates a result value from each group and its key.
325+
/// </summary>
326+
/// <param name="source">A <see cref="JArray"/> whose elements to group.</param>
327+
/// <param name="config">The <see cref="NewtonsoftJsonParsingConfig"/>.</param>
328+
/// <param name="keySelector">A string expression to specify the key for each element.</param>
329+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
330+
/// <returns>A <see cref="JArray"/> where each element represents a projection over a group and its key.</returns>
331+
[PublicAPI]
332+
public static JArray GroupBy(this JArray source, NewtonsoftJsonParsingConfig config, string keySelector, params object[]? args)
333+
{
334+
Check.NotNull(source);
335+
Check.NotNull(config);
336+
Check.NotNullOrEmpty(keySelector);
337+
338+
var queryable = ToQueryable(source, config);
339+
return ToJArray(() => queryable.GroupBy(config, keySelector, args));
340+
}
341+
#endregion
342+
306343
#region Last
307344
/// <summary>
308345
/// Returns the last element of a sequence that satisfies a specified condition.
@@ -813,7 +850,17 @@ private static JArray ToJArray(Func<IQueryable> func)
813850
var array = new JArray();
814851
foreach (var dynamicElement in func())
815852
{
816-
var element = dynamicElement is DynamicClass dynamicClass ? JObject.FromObject(dynamicClass) : dynamicElement;
853+
var element = dynamicElement switch
854+
{
855+
IGrouping<object, object> grouping => new JObject
856+
{
857+
[nameof(grouping.Key)] = JToken.FromObject(grouping.Key),
858+
["Values"] = ToJArray(grouping.AsQueryable)
859+
},
860+
DynamicClass dynamicClass => JObject.FromObject(dynamicClass),
861+
_ => dynamicElement
862+
};
863+
817864
array.Add(element);
818865
}
819866

src/System.Linq.Dynamic.Core.SystemTextJson/SystemTextJsonExtensions.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq.Dynamic.Core.SystemTextJson.Utils;
66
using System.Linq.Dynamic.Core.Validation;
77
using System.Text.Json;
8+
using JetBrains.Annotations;
89

910
namespace System.Linq.Dynamic.Core.SystemTextJson;
1011

@@ -371,6 +372,42 @@ public static JsonElement First(this JsonDocument source, string predicate, para
371372
}
372373
#endregion FirstOrDefault
373374

375+
#region GroupBy
376+
/// <summary>
377+
/// Groups the elements of a sequence according to a specified key string function
378+
/// and creates a result value from each group and its key.
379+
/// </summary>
380+
/// <param name="source">A <see cref="JsonDocument"/> whose elements to group.</param>
381+
/// <param name="keySelector">A string expression to specify the key for each element.</param>
382+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
383+
/// <returns>A <see cref="JsonDocument"/> where each element represents a projection over a group and its key.</returns>
384+
[PublicAPI]
385+
public static JsonDocument GroupBy(this JsonDocument source, string keySelector, params object[]? args)
386+
{
387+
return GroupBy(source, SystemTextJsonParsingConfig.Default, keySelector, args);
388+
}
389+
390+
/// <summary>
391+
/// Groups the elements of a sequence according to a specified key string function
392+
/// and creates a result value from each group and its key.
393+
/// </summary>
394+
/// <param name="source">A <see cref="JsonDocument"/> whose elements to group.</param>
395+
/// <param name="config">The <see cref="SystemTextJsonParsingConfig"/>.</param>
396+
/// <param name="keySelector">A string expression to specify the key for each element.</param>
397+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
398+
/// <returns>A <see cref="JsonDocument"/> where each element represents a projection over a group and its key.</returns>
399+
[PublicAPI]
400+
public static JsonDocument GroupBy(this JsonDocument source, SystemTextJsonParsingConfig config, string keySelector, params object[]? args)
401+
{
402+
Check.NotNull(source);
403+
Check.NotNull(config);
404+
Check.NotNullOrEmpty(keySelector);
405+
406+
var queryable = ToQueryable(source, config);
407+
return ToJsonDocumentArray(() => queryable.GroupBy(config, keySelector, args));
408+
}
409+
#endregion
410+
374411
#region Last
375412
/// <summary>
376413
/// Returns the last element of a sequence.
@@ -1037,7 +1074,17 @@ private static JsonDocument ToJsonDocumentArray(Func<IQueryable> func)
10371074
var array = new List<object?>();
10381075
foreach (var dynamicElement in func())
10391076
{
1040-
array.Add(ToJsonElement(dynamicElement));
1077+
var element = dynamicElement switch
1078+
{
1079+
IGrouping<object, object> grouping => ToJsonElement(new
1080+
{
1081+
Key = ToJsonElement(grouping.Key),
1082+
Values = ToJsonDocumentArray(grouping.AsQueryable).RootElement
1083+
}),
1084+
_ => ToJsonElement(dynamicElement)
1085+
};
1086+
1087+
array.Add(element);
10411088
}
10421089

10431090
return JsonDocumentUtils.FromObject(array);

test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,88 @@ public void FirstOrDefault()
168168
_source.FirstOrDefault("Age > 999").Should().BeNull();
169169
}
170170

171+
[Fact]
172+
public void GroupBySimpleKeySelector()
173+
{
174+
// Arrange
175+
var json =
176+
"""
177+
[
178+
{
179+
"Name": "Mr. Test Smith",
180+
"Type": "PAY",
181+
"Something": {
182+
"Field1": "Test1",
183+
"Field2": "Test2"
184+
}
185+
},
186+
{
187+
"Name": "Mr. Test Smith",
188+
"Type": "DISPATCH",
189+
"Something": {
190+
"Field1": "Test1",
191+
"Field2": "Test2"
192+
}
193+
},
194+
{
195+
"Name": "Different Name",
196+
"Type": "PAY",
197+
"Something": {
198+
"Field1": "Test3",
199+
"Field2": "Test4"
200+
}
201+
}
202+
]
203+
""";
204+
var source = JArray.Parse(json);
205+
206+
// Act
207+
var resultAsJson = source.GroupBy("Type").ToString();
208+
209+
// Assert
210+
var expected =
211+
"""
212+
[
213+
{
214+
"Key": "PAY",
215+
"Values": [
216+
{
217+
"Name": "Mr. Test Smith",
218+
"Type": "PAY",
219+
"Something": {
220+
"Field1": "Test1",
221+
"Field2": "Test2"
222+
}
223+
},
224+
{
225+
"Name": "Different Name",
226+
"Type": "PAY",
227+
"Something": {
228+
"Field1": "Test3",
229+
"Field2": "Test4"
230+
}
231+
}
232+
]
233+
},
234+
{
235+
"Key": "DISPATCH",
236+
"Values": [
237+
{
238+
"Name": "Mr. Test Smith",
239+
"Type": "DISPATCH",
240+
"Something": {
241+
"Field1": "Test1",
242+
"Field2": "Test2"
243+
}
244+
}
245+
]
246+
}
247+
]
248+
""";
249+
250+
resultAsJson.Should().Be(expected);
251+
}
252+
171253
[Fact]
172254
public void Last()
173255
{

test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ namespace System.Linq.Dynamic.Core.SystemTextJson.Tests;
66

77
public class SystemTextJsonTests
88
{
9+
private static readonly JsonSerializerOptions _options = new()
10+
{
11+
WriteIndented = true
12+
};
13+
914
private const string ExampleJsonObjectArray =
1015
"""
1116
[
@@ -142,7 +147,7 @@ public void Distinct()
142147
}
143148
]
144149
""";
145-
var source = JsonDocument.Parse(json);
150+
using var source = JsonDocument.Parse(json);
146151

147152
// Act
148153
var result = source.Select("Name").Distinct();
@@ -174,6 +179,89 @@ public void FirstOrDefault()
174179
_source.FirstOrDefault("Age > 999").Should().BeNull();
175180
}
176181

182+
[Fact]
183+
public void GroupBySimpleKeySelector()
184+
{
185+
// Arrange
186+
var json =
187+
"""
188+
[
189+
{
190+
"Name": "Mr. Test Smith",
191+
"Type": "PAY",
192+
"Something": {
193+
"Field1": "Test1",
194+
"Field2": "Test2"
195+
}
196+
},
197+
{
198+
"Name": "Mr. Test Smith",
199+
"Type": "DISPATCH",
200+
"Something": {
201+
"Field1": "Test1",
202+
"Field2": "Test2"
203+
}
204+
},
205+
{
206+
"Name": "Different Name",
207+
"Type": "PAY",
208+
"Something": {
209+
"Field1": "Test3",
210+
"Field2": "Test4"
211+
}
212+
}
213+
]
214+
""";
215+
using var source = JsonDocument.Parse(json);
216+
217+
// Act
218+
var result = source.GroupBy("Type");
219+
var resultAsJson = JsonSerializer.Serialize(result, _options);
220+
221+
// Assert
222+
var expected =
223+
"""
224+
[
225+
{
226+
"Key": "PAY",
227+
"Values": [
228+
{
229+
"Name": "Mr. Test Smith",
230+
"Type": "PAY",
231+
"Something": {
232+
"Field1": "Test1",
233+
"Field2": "Test2"
234+
}
235+
},
236+
{
237+
"Name": "Different Name",
238+
"Type": "PAY",
239+
"Something": {
240+
"Field1": "Test3",
241+
"Field2": "Test4"
242+
}
243+
}
244+
]
245+
},
246+
{
247+
"Key": "DISPATCH",
248+
"Values": [
249+
{
250+
"Name": "Mr. Test Smith",
251+
"Type": "DISPATCH",
252+
"Something": {
253+
"Field1": "Test1",
254+
"Field2": "Test2"
255+
}
256+
}
257+
]
258+
}
259+
]
260+
""";
261+
262+
resultAsJson.Should().Be(expected);
263+
}
264+
177265
[Fact]
178266
public void Last()
179267
{
@@ -265,7 +353,7 @@ public void OrderBy_Multiple()
265353
}
266354
]
267355
""";
268-
var source = JsonDocument.Parse(json);
356+
using var source = JsonDocument.Parse(json);
269357

270358
// Act
271359
var result = source.OrderBy("Age, Name").Select("Name");
@@ -279,7 +367,7 @@ public void OrderBy_Multiple()
279367
public void Page()
280368
{
281369
var json = "[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]";
282-
var source = JsonDocument.Parse(json);
370+
using var source = JsonDocument.Parse(json);
283371

284372
// Act
285373
var result = source.Page(2, 3);
@@ -293,7 +381,7 @@ public void Page()
293381
public void PageResult()
294382
{
295383
var json = "[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]";
296-
var source = JsonDocument.Parse(json);
384+
using var source = JsonDocument.Parse(json);
297385

298386
// Act
299387
var pagedResult = source.PageResult(2, 3);
@@ -339,7 +427,7 @@ public void SelectMany()
339427
]
340428
}]
341429
""";
342-
var source = JsonDocument.Parse(json);
430+
using var source = JsonDocument.Parse(json);
343431

344432
// Act
345433
var result = source

0 commit comments

Comments
 (0)