Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq.Dynamic.Core.NewtonsoftJson.Config;
using System.Linq.Dynamic.Core.NewtonsoftJson.Extensions;
using System.Linq.Dynamic.Core.Validation;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;

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

#region GroupBy
/// <summary>
/// Groups the elements of a sequence according to a specified key string function
/// and creates a result value from each group and its key.
/// </summary>
/// <param name="source">A <see cref="JArray"/> whose elements to group.</param>
/// <param name="keySelector">A string expression to specify the key for each element.</param>
/// <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>
/// <returns>A <see cref="JArray"/> where each element represents a projection over a group and its key.</returns>
[PublicAPI]
public static JArray GroupBy(this JArray source, string keySelector, params object[]? args)
{
return GroupBy(source, NewtonsoftJsonParsingConfig.Default, keySelector, args);
}

/// <summary>
/// Groups the elements of a sequence according to a specified key string function
/// and creates a result value from each group and its key.
/// </summary>
/// <param name="source">A <see cref="JArray"/> whose elements to group.</param>
/// <param name="config">The <see cref="NewtonsoftJsonParsingConfig"/>.</param>
/// <param name="keySelector">A string expression to specify the key for each element.</param>
/// <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>
/// <returns>A <see cref="JArray"/> where each element represents a projection over a group and its key.</returns>
[PublicAPI]
public static JArray GroupBy(this JArray source, NewtonsoftJsonParsingConfig config, string keySelector, params object[]? args)
{
Check.NotNull(source);
Check.NotNull(config);
Check.NotNullOrEmpty(keySelector);

var queryable = ToQueryable(source, config);
return ToJArray(() => queryable.GroupBy(config, keySelector, args));
}
#endregion

#region Last
/// <summary>
/// Returns the last element of a sequence that satisfies a specified condition.
Expand Down Expand Up @@ -813,7 +850,17 @@ private static JArray ToJArray(Func<IQueryable> func)
var array = new JArray();
foreach (var dynamicElement in func())
{
var element = dynamicElement is DynamicClass dynamicClass ? JObject.FromObject(dynamicClass) : dynamicElement;
var element = dynamicElement switch
{
IGrouping<object, object> grouping => new JObject
{
[nameof(grouping.Key)] = JToken.FromObject(grouping.Key),
["Values"] = ToJArray(grouping.AsQueryable)
},
DynamicClass dynamicClass => JObject.FromObject(dynamicClass),
_ => dynamicElement
};

array.Add(element);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq.Dynamic.Core.SystemTextJson.Utils;
using System.Linq.Dynamic.Core.Validation;
using System.Text.Json;
using JetBrains.Annotations;

namespace System.Linq.Dynamic.Core.SystemTextJson;

Expand Down Expand Up @@ -371,6 +372,42 @@ public static JsonElement First(this JsonDocument source, string predicate, para
}
#endregion FirstOrDefault

#region GroupBy
/// <summary>
/// Groups the elements of a sequence according to a specified key string function
/// and creates a result value from each group and its key.
/// </summary>
/// <param name="source">A <see cref="JsonDocument"/> whose elements to group.</param>
/// <param name="keySelector">A string expression to specify the key for each element.</param>
/// <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>
/// <returns>A <see cref="JsonDocument"/> where each element represents a projection over a group and its key.</returns>
[PublicAPI]
public static JsonDocument GroupBy(this JsonDocument source, string keySelector, params object[]? args)
{
return GroupBy(source, SystemTextJsonParsingConfig.Default, keySelector, args);
}

/// <summary>
/// Groups the elements of a sequence according to a specified key string function
/// and creates a result value from each group and its key.
/// </summary>
/// <param name="source">A <see cref="JsonDocument"/> whose elements to group.</param>
/// <param name="config">The <see cref="SystemTextJsonParsingConfig"/>.</param>
/// <param name="keySelector">A string expression to specify the key for each element.</param>
/// <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>
/// <returns>A <see cref="JsonDocument"/> where each element represents a projection over a group and its key.</returns>
[PublicAPI]
public static JsonDocument GroupBy(this JsonDocument source, SystemTextJsonParsingConfig config, string keySelector, params object[]? args)
{
Check.NotNull(source);
Check.NotNull(config);
Check.NotNullOrEmpty(keySelector);

var queryable = ToQueryable(source, config);
return ToJsonDocumentArray(() => queryable.GroupBy(config, keySelector, args));
}
#endregion

#region Last
/// <summary>
/// Returns the last element of a sequence.
Expand Down Expand Up @@ -1037,7 +1074,17 @@ private static JsonDocument ToJsonDocumentArray(Func<IQueryable> func)
var array = new List<object?>();
foreach (var dynamicElement in func())
{
array.Add(ToJsonElement(dynamicElement));
var element = dynamicElement switch
{
IGrouping<object, object> grouping => ToJsonElement(new
{
Key = ToJsonElement(grouping.Key),
Values = ToJsonDocumentArray(grouping.AsQueryable).RootElement
}),
_ => ToJsonElement(dynamicElement)
};

array.Add(element);
}

return JsonDocumentUtils.FromObject(array);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,88 @@ public void FirstOrDefault()
_source.FirstOrDefault("Age > 999").Should().BeNull();
}

[Fact]
public void GroupBySimpleKeySelector()
{
// Arrange
var json =
"""
[
{
"Name": "Mr. Test Smith",
"Type": "PAY",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Mr. Test Smith",
"Type": "DISPATCH",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Different Name",
"Type": "PAY",
"Something": {
"Field1": "Test3",
"Field2": "Test4"
}
}
]
""";
var source = JArray.Parse(json);

// Act
var resultAsJson = source.GroupBy("Type").ToString();

// Assert
var expected =
"""
[
{
"Key": "PAY",
"Values": [
{
"Name": "Mr. Test Smith",
"Type": "PAY",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Different Name",
"Type": "PAY",
"Something": {
"Field1": "Test3",
"Field2": "Test4"
}
}
]
},
{
"Key": "DISPATCH",
"Values": [
{
"Name": "Mr. Test Smith",
"Type": "DISPATCH",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
}
]
}
]
""";

resultAsJson.Should().Be(expected);
}

[Fact]
public void Last()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ namespace System.Linq.Dynamic.Core.SystemTextJson.Tests;

public class SystemTextJsonTests
{
private static readonly JsonSerializerOptions _options = new()
{
WriteIndented = true
};

private const string ExampleJsonObjectArray =
"""
[
Expand Down Expand Up @@ -142,7 +147,7 @@ public void Distinct()
}
]
""";
var source = JsonDocument.Parse(json);
using var source = JsonDocument.Parse(json);

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

[Fact]
public void GroupBySimpleKeySelector()
{
// Arrange
var json =
"""
[
{
"Name": "Mr. Test Smith",
"Type": "PAY",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Mr. Test Smith",
"Type": "DISPATCH",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Different Name",
"Type": "PAY",
"Something": {
"Field1": "Test3",
"Field2": "Test4"
}
}
]
""";
using var source = JsonDocument.Parse(json);

// Act
var result = source.GroupBy("Type");
var resultAsJson = JsonSerializer.Serialize(result, _options);

// Assert
var expected =
"""
[
{
"Key": "PAY",
"Values": [
{
"Name": "Mr. Test Smith",
"Type": "PAY",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
},
{
"Name": "Different Name",
"Type": "PAY",
"Something": {
"Field1": "Test3",
"Field2": "Test4"
}
}
]
},
{
"Key": "DISPATCH",
"Values": [
{
"Name": "Mr. Test Smith",
"Type": "DISPATCH",
"Something": {
"Field1": "Test1",
"Field2": "Test2"
}
}
]
}
]
""";

resultAsJson.Should().Be(expected);
}

[Fact]
public void Last()
{
Expand Down Expand Up @@ -265,7 +353,7 @@ public void OrderBy_Multiple()
}
]
""";
var source = JsonDocument.Parse(json);
using var source = JsonDocument.Parse(json);

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

// Act
var result = source.Page(2, 3);
Expand All @@ -293,7 +381,7 @@ public void Page()
public void PageResult()
{
var json = "[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]";
var source = JsonDocument.Parse(json);
using var source = JsonDocument.Parse(json);

// Act
var pagedResult = source.PageResult(2, 3);
Expand Down Expand Up @@ -339,7 +427,7 @@ public void SelectMany()
]
}]
""";
var source = JsonDocument.Parse(json);
using var source = JsonDocument.Parse(json);

// Act
var result = source
Expand Down
Loading