I have the following DTO:
class Permission
{
public string ObjectId { get; set; }
public string ObjectType { get; set; }
public string Scope { get; set; }
public string? AccountId { get; set; }
public string? GroupId { get; set; }
}
var permissions = new List<Permission>()
{
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = "1", GroupId = null },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = "2", GroupId = null },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = null, GroupId = "1" },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:write", AccountId = "2", GroupId = null },
new(){ ObjectId = "2", ObjectType = "link", Scope = "link:read", AccountId = "1", GroupId = null },
};
I want the following outcome:
{[
"1": {
"read": {
"accounts": ["1", "2"],
"groups": ["1", "2", "3"]
},
"write": {
"accounts": ["1", "2"],
"groups": ["1", "2", "3"]
}
}
]}
Basically an array of objects of Permission, grouped by their ObjectId property, and then grouped by each Scope that contains an array of the AccountId or GroupId properties.
Each Permission can have the same ObjectId but different Scope, AccountId and GroupId.
I tried to use GroupBy but that gives me an IGrouping and I'm not sure how to proceed.
You need more than just nested group bys. In your output JSON you have keys as property names. You also need to use ToDictionary to convert values into property keys.
var permissions = new List<Permission>()
{
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = "1", GroupId = null },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = "2", GroupId = null },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:read", AccountId = null, GroupId = "1" },
new(){ ObjectId = "1", ObjectType = "link", Scope = "link:write", AccountId = "2", GroupId = null },
new(){ ObjectId = "2", ObjectType = "link", Scope = "link:read", AccountId = "1", GroupId = null },
};
var result = permissions.GroupBy(r=> r.ObjectId).Select(r=> new {
r.Key,
InnerGroups = r.GroupBy(q=> q.Scope.Replace("link:","")).Select(q=> new {
Scope = q.Key,
Accounts = q.Where(z=> z.AccountId != null).Select(z=> z.AccountId).ToArray(),
Groups = q.Where(z=> z.GroupId != null).Select(z=> z.GroupId).ToArray()
})
})
.ToDictionary(r=> r.Key,r=> r.InnerGroups.ToDictionary(q=> q.Scope,q=> new {q.Accounts,q.Groups}));
var serialized = JsonSerializer.Serialize(result,new JsonSerializerOptions{ WriteIndented=true });
Here is the output :
{
"1": {
"read": {
"Accounts": [
"1",
"2"
],
"Groups": [
"1"
]
},
"write": {
"Accounts": [
"2"
],
"Groups": []
}
},
"2": {
"read": {
"Accounts": [
"1"
],
"Groups": []
}
}
}
Fiddle
You would have to use nested GroupBy to achieve that and then convert the result to the dictionaries:
var result = permissions
.GroupBy(p => p.ObjectId)
.ToDictionary(g => g.Key, g => g.GroupBy(g => g.Scope)
.ToDictionary(g => g.Key, g => new
{
accounts = g.Select(per => per.AccountId).Distinct().ToList(),
groups = g.Select(per => per.GroupId).Distinct().ToList()
}));
Related
I have a class:
public class User
{
public Guid ObjectId { get; set; }
public List<Guid> Groups { get; set; }
}
public class Group
{
public List<User> Users { get; set; }
}
Example:
Users:
[
{
"ObjectId": "1",
"Groups": ["G1"]
},
{
"ObjectId": "2",
"Groups": ["G2"]
},
{
"ObjectId": "3",
"Groups": ["G3"]
},
{
"ObjectId": "1",
"Groups": ["G4"]
}
]
Note that ObjectId "1" is present 2 times (One with G1, One with G4)
On running
var source = Group.SelectMany(x => x.Users).ToList();
I see the output as:
[
{
"ObjectId": "1",
"Groups": ["G1"]
},
{
"ObjectId": "2",
"Groups": ["G2"]
},
{
"ObjectId": "3",
"Groups": ["G3"]
}
]
How do I get the output as:
[
{
"ObjectId": "1",
"Groups": ["G1"]
},
{
"ObjectId": "2",
"Groups": ["G2"]
},
{
"ObjectId": "3",
"Groups": ["G3"]
},
{
"ObjectId": "1",
"Groups": ["G4"]
}
]
If the input is:
Users:
[
{
"ObjectId": "1",
"Groups": ["G1"]
},
{
"ObjectId": "2",
"Groups": ["G2"]
},
{
"ObjectId": "3",
"Groups": ["G3"]
},
{
"ObjectId": "1",
"Groups": ["G1"]
}
]
The output should be:
[
{
"ObjectId": "1",
"Groups": ["G1"]
},
{
"ObjectId": "2",
"Groups": ["G2"]
},
{
"ObjectId": "3",
"Groups": ["G3"]
}
]
UPDATE:
Apologize as my question was not clear:
Classes:
public class GroupMembership
{
public List<AzureADUser> SourceMembers { get; set; }
}
public class AzureADUser
{
public Guid ObjectId { get; set; }
public List<Guid> SourceGroups { get; set; }
}
var users1 = new List<AzureADUser> {
new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {new Guid("GuidG1")}},
new () { ObjectId = new Guid("Guid2"), SourceGroups = new List<Guid> {new Guid("GuidG2")}},
new () { ObjectId = new Guid("Guid3"), SourceGroups = new List<Guid> {new Guid("GuidG3")}},
new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {new Guid("GuidG4")}} //include this
};
var users2 = new List<AzureADUser> {
new () { ObjectId = new Guid("Guid1"), SourceGroups = new List<Guid> {<GuidG1>}} // remove this as this is a duplicate
};
var groupMembership1 = new GroupMembership
{
SourceMembers = users1;
};
var groupMembership2 = new GroupMembership
{
SourceMembers = users2;
};
var groupsMemberships = new List<GroupMembership>();
groupsMemberships.Add(groupMembership1);
groupsMemberships.Add(groupMembership2);
/* output:
ObjectId: new Guid("Guid1"), SourceGroups: new Guid("Guid1")
ObjectId: new Guid("Guid2"), SourceGroups: new Guid("Guid2")
ObjectId: new Guid("Guid3"), SourceGroups: new Guid("Guid3")
ObjectId: new Guid("Guid1"), SourceGroups: new Guid("GuidG4")
*/
SelectMany isn't the problematique API here, it just collects the existing sub-collections of Users into one. It's doesn't remove any and doesn't look at the data at all.
If your goal is to remove duplicates from your list, you could use LINQ's Distinct method on the result of SelectMany - and provide the appropriate IEqualityComparer that checks the User data for equaility. The comparison should return true if all values, ObjectID and all elements in Group, are equal.
Your code is somewhat confusing: Your JSON deals with ints and strings, but the class defs uses GUIDs. I'll use the JSON definition for the following example:
public static void Main()
{
var users = new List<User> {
new () { ObjectId = 1, Groups = new () {"G1"}},
new () { ObjectId = 2, Groups = new () {"G2"}},
new () { ObjectId = 3, Groups = new () {"G3"}},
new () { ObjectId = 1, Groups = new () {"G4"}}, // should remain
new () { ObjectId = 1, Groups = new () {"G1"}}, // should be removed equal to 1st
};
var distinct = users.Distinct(new UserComp());
foreach (var usr in distinct)
Console.WriteLine(usr);
/* output:
ID: 1, Groups: G1
ID: 2, Groups: G2
ID: 3, Groups: G3
ID: 1, Groups: G4
*/
}
public class UserComp : IEqualityComparer<User> {
public bool Equals (User usr1, User usr2)
{
if (usr1 is null) return usr2 is null;
if (usr2 is null) return false;
return usr1.ObjectId == usr2.ObjectId && ((usr1.Groups is {} g1 && usr2.Groups is {} g2 && g1.SequenceEqual(g2)) || (usr1.Groups is null && usr2.Groups is null));
}
// hash is kind of weak ignoring the group data, but suitable for the demo
public int GetHashCode (User usr) => usr?.ObjectId ?? 0;
}
public class User
{
public int ObjectId { get; set; }
public List<string> Groups { get; set; }
public override string ToString () => $"ID: {ObjectId}, Groups: {(Groups == null ? "-" : string.Join(", " ,Groups))}";
}
Note that you don't need an explicit equaility comparer class if you put the same logic into the override of User.Equals.
I have a c# class that looks something like this:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
}
and i would like to convert it to json where json property name is item name and its value is item description. if some item has any children then i would like the json property name to stay as item name and the children to be added like item name:item description(the parent item description and type become empty string if it has any children, except when the type is array). The type has following values: array, string, int, object. if the item type is an array then each time a child is added to the type array item, the child is item description. so i would like those values to be added to the json array as well.
if the type is string or int the json property value should be int or string.
I am trying to write custom JsonSerializer but i am getting nowhere.
so if i have a list with items:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
then the json should like this:
{
"name1":{
"name2":"description1",
"name3":[
"ArrayItem1",
"ArrayItem2",
"ArrayItem1"
]
},
"name4":5
}
Here's an extension method that I think does what you want:
public static class Ex
{
public static string ToJson(this List<Item> items)
{
var lookup = items.ToLookup(x => x.ParentId);
JObject ToJson(int? parentId)
{
JProperty ToProperty(Item item)
{
switch (item.Type)
{
case "":
return new JProperty(item.Name, ToJson(item.Id));
case "String":
return new JProperty(item.Name, item.Description);
case "Array":
return new JProperty(item.Name, lookup[item.Id].Select(x => x.Description).ToArray());
case "Int":
return new JProperty(item.Name, int.Parse(item.Description));
default:
return new JProperty(item.Name);
}
}
return new JObject(lookup[parentId].Select(x => ToProperty(x)));
}
var output = ToJson(null);
var text = Newtonsoft.Json.JsonConvert.SerializeObject(output, Newtonsoft.Json.Formatting.Indented);
return text;
}
}
I run it like this:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
Console.WriteLine(MyItems.ToJson());
And I get this out:
{
"Name1": {
"Name2": "Description1",
"Name3": [
"ArrayItem1",
"ArrayItem2",
"ArrayItem3"
]
},
"Name4": 5
}
i have two lists -plans and divisions with different properties ..
Sample data:
{
"plans": [
{
"planCode": "A",
"planShortName": "Apple",
"planType": null,
"bisPlanCode": "878",
},
{
"planCode": "B",
"planShortName": "Ball",
"planType": null,
"bisPlanCode": "536",
}
],
"divisions": [
{
"planCode": "878",
"divisions": [
{ divisionCode: "2", divisionName: "test2" },
{ divisionCode: "1", divisionName: "test1" }]
},
{
"planCode": "536",
"divisions": [
{ divisionCode: "3", divisionName: "test3" },
{ divisionCode: "1", divisionName: "test1" }
]
}
]
}
How to combine both these lists- plans and divsions into one list planDivisions on a condition where plans.bisPlanCode == divisions.plansCode in C#.
so the final result should look like
"planDivisions": [
{
"planCode": "A",
"planShortName": "Apple",
"planType": null,
"bisPlanCode": "878",
"divisions": [ { divisionCode: "2", divisionName: "test2" },
{ divisionCode: "1", divisionName: "test1" }]
},
{
"planCode": "B",
"planShortName": "Ball",
"planType": null,
"bisPlanCode": "536",
"divisions": [
{ divisionCode: "3", divisionName: "test3" },
{ divisionCode: "1", divisionName: "test1"
}]
}
]
What i have tried to do :
List<Data> planDivisions = new List<Data>();
Divisions.ForEach(division =>
{
var plan = Plans.Find(p => p.PlanCode == division.PlanCode);
if (plan != null)
{
Data data = new Data();
data.DivisionData = division;
data.PlanData = plan;
planDivisions.Add(data);
}
});
Is there any effecient way to do ?
You can use LINQ to join both data lists. More about it here.
class Plan {
public int PlanCode { get; set; }
public string PlanShortName { get; set; }
}
class Division {
public int DivisionCode { get; set; }
public string DivisionName { get; set; }
}
class DivisionAssignment {
public int PlanCode { get; set; }
public List<Division> Divisions { get; set; }
}
public static void JoinExample() {
// do your deserialization stuff here
List<Plan> plans = ...;
List<DivisionAssignment> assignments = ...;
// join the data
var query = from plan in plans
join assignment in assignments on plan.PlanCode equals assignment.PlanCode
select new { PlanCode = plan.PlanCode, Divisions = assignment.Divisions };
// reach the joined data
foreach (var planDivision in query) {
... = planDivision.PlanCode;
... = planDivision.Divisions;
...
}
}
I have two lists,
private List<DealResponse> L1 = new List<DealResponse>
{
new DealResponse {detailId = "5", detailcd = "ABC", fileName = "string 1", isNgo = "0"},
new DealResponse {detailId = "5", detailcd = "DEF", fileName = "string 2", isNgo = "0"},
new DealResponse {detailId = "5", detailcd = "XYZ", fileName = "string ", isNgo = "0"}
};
private List<DealResponse> L2 = new List<DealResponse>
{
new DealResponse {detailId = "5", detailcd = "ABC", fileName = "string 11", isNgo = "1"},
new DealResponse {detailId = "6", detailcd = "MNO", fileName = "string 3", isNgo = "1"}
};
I'm trying to write a method which accepts detailId and return the result by merging above two list, and if there is duplicate (duplicate definition = when detailId, detailcd match between two lists) select entry from L2
so after merging result would be
var Result = new List<DealResponse>
{
new DealResponse {detailId = "5", detailcd = "ABC", fileName = "string 11", isNgo = "1"},
new DealResponse {detailId = "5", detailcd = "DEF", fileName = "string 2", isNgo = "0"},
new DealResponse {detailId = "5", detailcd = "XYZ", fileName = "string ", isNgo = "0"},
new DealResponse {detailId = "6", detailcd = "MNO", fileName = "string 3", isNgo = "1"},
};
Note that in result we selected this entry from L2 since detailId = 5, detailcd = ABC was duplicate
public List<DealResponse> GetDealResponse(string detailId)
{
// My main confusion is while doing union how to handle the case
// which I mentioned (On duplicate select entry from second list)
var L3 = L1.Union(L2, new DealResponseComprarer()).ToList();
}
public class DealResponse
{
public string detailId { get; set; }
public string detailcd { get; set; }
public string fileName { get; set; }
public string isNgo { get; set; }
}
public class DealResponseComprarer : IEqualityComparer<DealResponse>
{
public bool Equals(DealResponse x, DealResponse y)
{
return x.detailId == y.detailId && x.detailcd == y.detailcd ;
}
public int GetHashCode(DealResponse obj)
{
return (obj.detailId.GetHashCode() + obj.detailcd.GetHashCode());
}
}
In case you actually want to filter your results using detailId, since this value is passed to the GetDealResponse() method, you could add a .Where condition to the equalized Union list.
public class DealResponse
{
public string detailId { get; set; }
public string detailcd { get; set; }
public string fileName { get; set; }
public string isNgo { get; set; }
}
public List<DealResponse> GetDealResponse(string detailId)
{
return L2.Union(L1, new DealResponseComprarer())
.Where(elm => elm.detailId.Equals(detailId)).ToList();
}
L1 = new List<DealResponse>() {
new DealResponse() { detailId = "5", detailcd = "ABC" , fileName = "string 1", isNgo = "0" },
new DealResponse() { detailId = "5", detailcd = "DEF" , fileName = "string 2", isNgo = "0" },
new DealResponse() { detailId = "5", detailcd = "XYZ" , fileName = "string ", isNgo = "0" }};
L2 = new List<DealResponse>() {
new DealResponse() { detailId = "5", detailcd = "ABC" , fileName = "string 11", isNgo = "1" },
new DealResponse() { detailId = "6", detailcd = "MNO" , fileName = "string 3", isNgo = "1" }};
string ID = "5";
List<DealResponse> L3 = GetDealResponse(ID);
Which would return this list:
{ detailId = "5", detailcd = "ABC" , fileName = "string 11", isNgo = "1" }
{ detailId = "5", detailcd = "DEF" , fileName = "string 2", isNgo = "0" }
{ detailId = "5", detailcd = "XYZ" , fileName = "string ", isNgo = "0" }
I'm trying to write a method which accepts detailId and return the result by merging above two list, and if there is duplicate (duplicate definition = when detailId, detailcd match between two lists) select entry from L2
Another way to say this is:
Start with L2
Add everything from L1 that isn't already in L2
This can be accomplished with one line:
var combined = L2.Concat(L1.Except(L2, new DealResponseComprarer()));
Example on DotNetFiddle
In my MVC3 application I want to create an anonymous collection with fields names like this:
new
{
Buyer.Firstname = "Jim",
Buyer.Lastname = "Carrey",
Phone = "403-222-6487",
PhoneExtension = "",
SmsNumber = "",
Buyer.Company = "Company 10025",
Buyer.ZipCode = "90210",
Buyer.City = "Beverly Hills",
Buyer.State = "CA",
Buyer.Address1 = "Address 10025"
Licenses[0].IsDeleted = "False",
Licenses[0].ID = "6",
Licenses[0].AdmissionDate = "2,1999",
Licenses[0].AdmissionDate_monthSelected = "2",
}
I want to have this in order to send custom post requests during integration testing of my app. How can I declare a an anonymous collection with this field names?
Use an anonymous collection of anonymous objects, like so:
Licenses = new [] {
new {
IsDeleted = "False",
ID = "6",
AdmissionDate = "2,1999",
AdmissionDate_monthSelected = "2"
} //, ... and so on
}
... and in context: ([edit] Oh, and I didn't see your buyer...)
new
{
Buyer = new {
Firstname = "Jim",
Lastname = "Carrey",
Company = "Company 10025",
ZipCode = "90210",
City = "Beverly Hills",
State = "CA",
Address1 = "Address 10025",
},
Phone = "403-222-6487",
PhoneExtension = "",
SmsNumber = "",
Licenses = new [] {
new {
IsDeleted = "False",
ID = "6",
AdmissionDate = "2,1999",
AdmissionDate_monthSelected = "2"
}
}
}
You could use object and collection initializer syntax:
var anonymousObject = new
{
Phone = "403-222-6487",
PhoneExtension = "",
SmsNumber = "",
Buyer = new
{
Firstname = "Jim",
Lastname = "Carrey",
Company = "Company 10025",
ZipCode = "90210",
City = "Beverly Hills",
State = "CA",
Address1 = "Address 10025"
},
Licenses = new[]
{
new
{
IsDeleted = "False",
ID = "6",
AdmissionDate = "2,1999",
AdmissionDate_monthSelected = "2",
}
}
}
Try this:
var x = new {
Phone = "403-222-6487",
PhoneExtension = "",
SmsNumber = "",
Buyer = new {
Firstname = "Jim",
Lastname = "Carrey",
Company = "Company 10025",
ZipCode = "90210",
City = "Beverly Hills",
State = "CA",
Address1 = "Address 10025"
},
Licenses = new[] {
new {
IsDeleted = "False",
ID = "6",
AdmissionDate = "2,1999",
AdmissionDate_monthSelected = "2"},
new {
IsDeleted = "True",
ID = "7",
AdmissionDate = "17,2001",
AdmissionDate_monthSelected = "3"}
}
};
Note: I am using a nested anonymous type for buyers and a nested array of yet another anyonymous type for licences. This allows you to access values like this
string name = x.Buyer.Lastname;
string id = x.Licences[0].ID;