c# Web API JSon combine Objects into Single Property - c#

I am working on a Web API in C# and am getting my data from a SQL Database. When I put a single student number in the GET call, their data is return correctly, however if they have two rows in the SQL query, then Return in the GET call shows their data in two separate properties. Is there a way to combine List Objects for a user who has two different rows of data into one single return. For instance, the below User belongs to two departments (Politics and Religions), and when I call the GET method, they appear twice, once for each Department;
{
"roles": [
[
"STUDENT",
"UG",
2,
"Politics",
"Smith, John",
123456
],
[
"STUDENT",
"UG",
2,
"Religions",
"Smith, John",
123456
]
]
}
What I would like to be return is this;
{
"roles": [
[
"STUDENT",
"UG",
2,
"Politics",
"Religions",
"Smith, John",
123456
]
]
}
I am retrieving the data from a SQL Server DB via a query, and have created a list to which I am adding the data from the query as Objects. When I search for a student using their ID, and if they have two different departments, I'd like for the 2 properties to be returned as a single one.
I have attempted the below, but this is as far as I've gotten;
int howmanyrecords = studentList.Count;
if (howmanyrecords >1)
{
foreach(Object stu in studentList)
{
var firstObject = studentList.ElementAt(0);
}
}
The above studentList.ElementAt(0) retrieves the first instance in the studentList i.e. the entry which has Politics as the department, but I'm not sure how to complete the above and how to merge into one object - my initial thought is, to compare all the indexes of both Objects and if they're different, add the value at Index X from Object2 into Object1.
Any pointers would be appreciated.

this code you want
var jObj = JObject.Parse(json);
var roles = (JArray)jObj["roles"];
if (roles.Count() > 1)
{
jObj["roles"] = new JArray(roles[0].Union(roles[1]));
json = jObj.ToString();
}
IMHO this code you need
if (roles.Count() > 1)
{
for (int i = 0; i < roles[0].Count(); i++)
{
if (roles[0][i].ToString() != roles[1][i].ToString())
{
var arr = new JArray();
arr.Add(roles[0][i]);
arr.Add(roles[1][i]);
roles[0][i] = arr;
}
}
roles[1].Remove();
json = jObj.ToString();
}
output
{
"roles": [
[
"STUDENT",
"UG",
2,
[
"Politics",
"Religions"
],
"Smith, John",
123456
]
]
UPDATE
This will be working for any number of students
var jObj = JObject.Parse(json);
var roles = (JArray)jObj["roles"];
if (roles.Count() > 1)
{
for (int j = 0; j < roles[0].Count(); j++)
{
var jArr = new JArray();
for (var i = 1; i < roles.Count(); i++)
{
if (i == 1) jArr = new JArray { roles[0][j] };
jArr.Add(roles[i][j]);
}
var arr = jArr.Distinct();
if (arr.Count() > 1) roles[0][j] = new JArray(arr); // ["Politics", "Religions"]
//or if you dont want an array
if (arr.Count() > 1) roles[0][j] = string.Join(",", arr); // "Politics,Religions"
}
var c = roles.Count();
for (var i = c - 1; i > 0; i--)
roles[i].Remove();
json = jObj.ToString().Dump();
}

Related

replace an element in a specific array with another value in C#

There is one array for me. my array is as follows.
var Array = [["Dog","0","A","eat"],["cat","1","B","eat"]]
I want to replace the value in some indexes in this array with other values.
for example, it should be like this.
var newArray = [["Dog","big","house","eat"],["cat","small","forest","eat"]]
can be understood from the example, "0 = big, 1 = small" and "A=house, B=forest"
how can I solve this both with the for loop and using C# Linq.
Unsure if it qualifies as elegant but what you're describing is a matter of translation, a Dictionary is very good for this.
Loop through each string in each array and replace if the translation dictionary contains a key equal to the string.
var Array = new string[][] {
new string[] {"Dog", "0", "A", "eat" },
new string[] {"Cat", "1", "B", "eat" }
};
//Array: [["Dog","0","A","eat"],["Cat","1","B","eat"]]
var TranslationDict = new Dictionary<string, string>() {
{ "0", "big" },
{ "1", "small" },
{ "A", "house" },
{ "B", "forest" },
};
for (int y = 0; y < Array.Length; y++) {
for (int x = 0; x < Array[y].Length; x++) {
if(TranslationDict.ContainsKey(Array[y][x])) {
Array[y][x] = TranslationDict[Array[y][x]];
}
}
}
//Array: [["Dog","big","house","eat"],["Cat","small","forest","eat"]]
Do it with linq like that:
var testArray = array.
Select(x => x.
Select(y => y.Replace("0", "big").Replace("1","test")).ToArray())
.ToArray();

Filter JSON Array with dynamic conditions

I have many JSON array with different types of nodes in it.
Sample Json 1:
[
{
"EmpID": "23",
"EmpName": "Jhon",
"Age": "23"
},
{
"EmpID": "29",
"EmpName": "Paul",
"Age": "25"
},
{
"EmpID": "123",
"EmpName": "Jack",
"Age": "29"
},
{
"EmpID": "129",
"EmpName": "Apr",
"Age": "29"
}
]
Sample Json 2
[
{
"DepID": "2",
"Name": "Sales"
},
{
"DepID": "5",
"Name": "Marketing"
},
{
"DepID": "12",
"Name": "IT"
}
]
I want to filter them based on different conditions such as
1)EmpID=29
This should return
[
{
"EmpID": "29",
"EmpName": "Paul",
"Age": "25",
}
]
2)Age=23 and EmpName=Jhon
This should return
[
{
"EmpID": "23",
"EmpName": "Jhon",
"Age": "23"
}
]
Age=29
This should return
[
{
"EmpID": "123",
"EmpName": "Jack",
"Age": "29"
},
{
"EmpID": "129",
"EmpName": "Apr",
"Age": "29"
}
]
So I need a generic approach to do any number of filters on the JSON array. I am planning to get all the filters using some comma separated string like Age="23",EmpName="Jhon" and this can be converted to any format in the code.
I have tried creating dynamic filter using Json Path such as $.[?(#.Age == '23' && #.EmpName == 'Jhon')].
Also I tried using LINQ like
var result = JsonConvert.DeserializeObject(jsonString);
var res = (result as Newtonsoft.Json.Linq.JArray).Where(x =>
x["Age"].ToString() =="23" && x["EmpName"].ToString()=="Jhon").ToList();
But how I can generate the where conditions dynamically based on any number of conditions I receive
Also there is a plan to include Date filters in case there is some datetime nodes in json such as BirthDate>12051995.
I am not sure how I can dynamically filter using any number of input filter conditions.
To get this working in a traditional way, you'll need to perform 3 steps:
define a class to contain the data
deserialize the json into a list of objects
use linq to query your selection
You can do the same thing for the departments.
If you need to join them in any way, use .Join. If the JSON is mixed, you can create a single class containing all the properties and use that to query.
So for the simple case: first define a class to represent you object:
public class Employee
{
public int EmpID {get;set;}
public string EmpName {get;set;}
public int Age {get;set;}
}
Then deserialize and query:
put at the top:
using System.Text.Json;
public void Main()
{
//deserialize into a list
List<Employee> employees =
JsonSerializer.Deserialize<List<Employee>>(yourJsonString);
//query
var result = employees.Where(c => c.Age == 23 && c.EmpName == "Jhon");
//show results
foreach (var employee in result)
Console.WriteLine(employee.EmpID);
}
As by update:
Depending on your use case you have a couple of options:
a fixed number of dynamic properties
a truly dynamic query
A fixed number of dynamic properties
You can achieve a more dynamic setup with the following:
//define the filterable properties
//note they are nullable
int? age = null;
int? id = null;
string name = null;
//apply them in a query
//
//note: if one of the filter properties is not set,
// that side of the && expression evaluates to "true"
var result = employees.Where(c => (age == null ? true : c.Age == age) &&
(id == null ? true : c.EmpId == id) &&
(name == null ? true : c.EmpName == name));
a truly dynamic query
Now here things start to get tricky. One possible option is to generate a string based query, with the help of a libary like Dynamic Linq
You have almost nailed it. :)
Instead of using DeserializeObject and then converting it to JArray prefer JArray.Parse
var json = File.ReadAllText("sample.json");
var semiParsedJson = JArray.Parse(json);
Instead of using ToList after Where prefer JArray constructor which can work well with an IEnumerable<JToken>
const string IdField = "EmpID", NameField = "EmpName", AgeField = "Age";
const StringComparison caseIgnorant = StringComparison.OrdinalIgnoreCase;
var idEq29 = semiParsedJson.Children()
.Where(token => string.Equals(token[IdField].Value<string>(),"29", caseIgnorant));
Console.WriteLine(new JArray(idEq29).ToString());
The other queries can be implemented in the very same way
var ageEq23AndNameJhon = semiParsedJson.Children()
.Where(token => string.Equals(token[AgeField].Value<string>(), "23", caseIgnorant)
&& string.Equals(token[NameField].Value<string>(), "Jhon", caseIgnorant));
Console.WriteLine(new JArray(ageEq23AndNameJhon).ToString());
var ageEq29 = semiParsedJson.Children()
.Where(token => string.Equals(token[AgeField].Value<string>(), "29", caseIgnorant));
Console.WriteLine(new JArray(ageEq29).ToString());
UPDATE #1: Enhance proposed solution
With the following extension method
public static class JArrayExtensions
{
public static JArray Filter(this JArray array, Func<JToken, bool> predicate)
=> new JArray(array.Children().Where(predicate));
}
you can greatly simplify the filtering
var idEq29 = semiParsedJson
.Filter(token => string.Equals(token[IdField].Value<string>(),"29", caseIgnorant));
var ageEq23AndNameJhon = semiParsedJson
.Filter(token => string.Equals(token[AgeField].Value<string>(), "23", caseIgnorant))
.Filter(token => string.Equals(token[NameField].Value<string>(), "Jhon", caseIgnorant));
var ageEq29 = semiParsedJson
.Filter(token => string.Equals(token[AgeField].Value<string>(), "29", caseIgnorant));
Console.WriteLine(idEq29);
Console.WriteLine();
Console.WriteLine(ageEq23AndNameJhon);
Console.WriteLine();
Console.WriteLine(ageEq29);
Or you can push it even further. If all the fields store string values then you can define the extension method like this:
public static class JArrayExtensions
{
public static JArray Filter(this JArray array, string field, string value)
=> new JArray(array.Children().Where(GenerateFilter(field, value)));
private static Func<JToken, bool> GenerateFilter(string field, string value)
=> (JToken token) => string.Equals(token[field].Value<string>(), value, StringComparison.OrdinalIgnoreCase);
}
The the filter queries are super simple :D
var idEq29 = semiParsedJson
.Filter(IdField,"29");
var ageEq23AndNameJhon = semiParsedJson
.Filter(AgeField, "23")
.Filter(NameField, "Jhon");
var ageEq29 = semiParsedJson
.Filter(AgeField, "29");
Console.WriteLine(ageEq23AndNameJhon);
Console.WriteLine();
Console.WriteLine(idEq29);
Console.WriteLine();
Console.WriteLine(ageEq29);

Grouping data based on date entity framework and LINQ

I have a array of analytic events in my database and i would like to send this data grouped by date to my client app.
The data from the db looks something like this (but with hundreds of records):
[
{
"DateAdded": "2006-12-30 00:38:54",
"Event": "click",
"Category": "externalWebsite"
},
{
"DateAdded": "2006-07-20 00:36:44",
"Event": "click",
"Category": "social"
},
{
"DateAdded": "2006-09-20 00:36:44",
"Event": "click",
"Category": "social"
},
{
"DateAdded": "2006-09-22 00:12:34",
"Event": "load",
"Category": "profile"
}
]
What I would like to do is return the count of all the say 'social' 'click' but by month so it would look like this:
[
{
"name": "socialclicks",
"series": [
{
"count": 259,
"name": "Jan"
},
{
"count": 0,
"name": "Feb"
},
{
"count": 52,
"name": "Mar"
}
... etc, etc up to Dec <====
]
}
]
So, what I have been trying is to get all the records that are associated with a particular user using their id. This is simple.
Now I need to split them records into monthly counts showing the last 12 months from the current month (if the month doesn't exist return 0) - this is proving to be complicated and difficult.
My approach was this:
var records = context.records.where(r => r.Id = userId).ToList();
var jan
var feb
var mar
var apr
... etc, etc
for (int i = 0; i < records.Count ; i++)
{
if (record.DateAdded > "2005-12-31 00:00.00" && record.DateAdded < "2006-01-31 00:00.00") {
jan++;
}
if (record.DateAdded > "2006-01-31 00:00.00" && record.DateAdded < "2006-02-28 00:00.00") {
feb++;
}
...etc, etc
}
Then i use these variables to count and hard code the name for the returned data.
As you can see, there is lots of etc, etc because the code has become ridiculous!
There must be a more simple way to do this but i cant seem to find one!
Any assistance would be appreciated.
Thanks
The first thing to do is group all your data by the 2 properties you're interested in
Event
Category
Example:
var partialResult = entries.GroupBy(x => new {
x.Event,
x.Category
});
From there, when you project your result and you can group again by Month & Year. - anonymous object used for demo, but you could easily define this as a struct/class as appropriate:
var result = entries.GroupBy(x => new {
x.Event,
x.Category
}).Select(g => new {
g.Key.Event,
g.Key.Category,
Series = g.GroupBy(x => new {x.DateAdded.Month, x.DateAdded.Year})
.Select(i => new{
i.Key.Month,
i.Key.Year,
Count = i.Count()
}).ToArray()
});
foreach(var item in result)
{
Console.WriteLine($"Event:{item.Event} Category:{item.Category}");
foreach(var serie in item.Series)
Console.WriteLine($"\t{CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(serie.Month)}{serie.Year} Count={serie.Count}");
}
Edit: To satisfy your requirement that:
if the month doesn't exist return 0
You need to add a few complexities. First a method which can work out all the Month/Year combinations between 2 dates.
private static IEnumerable<(int Month, int Year)> MonthsBetween(
DateTime startDate,
DateTime endDate)
{
DateTime iterator;
DateTime limit;
if (endDate > startDate)
{
iterator = new DateTime(startDate.Year, startDate.Month, 1);
limit = endDate;
}
else
{
iterator = new DateTime(endDate.Year, endDate.Month, 1);
limit = startDate;
}
var dateTimeFormat = CultureInfo.CurrentCulture.DateTimeFormat;
while (iterator < limit)
{
yield return (iterator.Month,iterator.Year);
iterator = iterator.AddMonths(1);
}
}
Also you'll need some kind of range to both calculate all the months between, as well as filter your original query:
var dateRangeStart = DateTime.Parse("2006-01-01");
var dateRangeEnd = DateTime.Parse("2007-01-01");
var monthRange = MonthsBetween(dateRangeStart,dateRangeEnd);
var results = entries.Where(e => e.DateAdded>=dateRangeStart && e.DateAdded<dateRangeEnd)
..... etc
And then, when outputting results you need to effectively do a left join onto your list of years/months. For some reason this is easier using query syntax than lambda.
foreach(var item in results)
{
Console.WriteLine($"Event:{item.Event} Category:{item.Category}");
var joinedSeries = from month in monthRange
join serie in item.Series
on new{month.Year, month.Month} equals new {serie.Year, serie.Month} into joined
from data in joined.DefaultIfEmpty()
select new {
Month = data == null ? month.Month : data.Month,
Year = data == null ? month.Year : data.Year,
Count = data == null ? 0 : data.Count
};
foreach(var serie in joinedSeries)
Console.WriteLine($"\t{CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(serie.Month)}{serie.Year} Count={serie.Count}");
}
Live example: https://dotnetfiddle.net/K7ZoJN

Find pattern in json with json.net and linq

I'm searching a json file, with the following structure:
{
"objects": [
{
"name": "obj1",
"state": {
"type": 4,
"childs": [
"state": {
"type": 5,
...
The state can contain state as a child until any number of Levels. Now im trying to find all objects containing a certain Patterns of states, e.g. state 4 with child state 5 with child state 2.
My code so far is this.
JObject o = JObject.Parse(System.IO.File.ReadAllText(#"j.json"));
var oObjects=
from p in o["objects"]
where (string)p["state"] == "4"
select (string)p["name"];
How can I expand the code to find all objects containing the search pattern on any Level?
To make it work for indefinite level, then you will need to use a recursive method like the following:
void Main()
{
var str = #"{
""objects"": [
{
""name"": ""obj1"",
""state"": {
""type"": 4,
""childs"": [
{
""state"": {
""type"": 5
}
}
]
}
}
]
}";
var obj = JObject.Parse(str);
GetValidObjects(obj, new string[] { "4", "5" }); // Name list of valid objects
}
And the helper methods defined like:
public IEnumerable<string> GetValidObjects(JObject obj, IEnumerable<string> values)
{
return obj["objects"]
.Where(i => (string)i["state"]["type"] == values.First() && ContainsState((JArray)i["state"]["childs"], values.Skip(1)))
.Select(i => (string)i["name"]);
}
public bool ContainsState(JArray childs, IEnumerable<string> values)
{
if (childs == null)
{
return values.Count() == 0;
}
return childs.Any(i => (string)i["state"]["type"] == values.First() && ContainsState((JArray)i["state"]["childs"], values.Skip(1)));
}
An option could be to convert the json to xml and then use an xpath query to obtain the list of nodes.
string json = System.IO.File.ReadAllText(#"j.json");
XmlDocument document = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
XmlNodeList nodes = document.SelectNodes("//name[../state[type[.=4] and childs/state[type[.=5] and childs/state[type[.=2]]]]]");
You can use SelectTokens for this:
var objects = o.SelectTokens("$.objects[?(#.state.type == 4
&& #.state.childs[*].state.type == 5)].name")
.Select(s => (string)s)
.ToList();

Finding differences within 2 Lists of string arrays

I am looking to find the differences between two Lists of string arrays using the index 0 of the array as the primary key.
List<string[]> original = new List<string[]>();
List<string[]> web = new List<string[]>();
//define arrays for List 'original'
string[] original_a1 = new string[3]{"a","2","3"};
string[] original_a2 = new string[3]{"x","2","3"};
string[] original_a3 = new string[3]{"c","2","3"};
//define arrays for List 'web'
string[] web_a1 = new string[3]{"a","2","3"};
string[] web_a2 = new string[3]{"b","2","3"};
string[] web_a3 = new string[3]{"c","2","3"};
//populate Lists
original.Add(original_a1);
original.Add(original_a2);
original.Add(original_a3);
web.Add(web_a1);
web.Add(web_a2);
web.Add(web_a3);
My goal is to find what is in List 'original' but NOT in 'web' by using index 0 as the primary key
This is what I tried.
List<string> differences = new List<string>(); //differences go in here
string tempDiff = ""; // I use this to try and avoid duplicate entries but its not working
for(int i = 0; i < original.Count; i++){
for(int j = 0; j< web.Count; j++){
if(!(original[i][0].Equals(web[j][0]))){
tempDiff = original[i][0];
}
}
differences.Add(tempDiff);
}
OUTPUT:
foreach(string x in differences){
Console.WriteLine("SIZE " + differences.Count);
Console.WriteLine(x);
ConSole.ReadLine();
}
SIZE 3
SIZE 3
x
SIZE 3
x
Why is it reporting the mismatch 3 times instead of once?
Using linq you can just go:
var differences = orignal.Except(web).ToList();
Reference here
This will give you the values that are in original, that don't exist in web
Sorry didn't read your question properly, to answer your question:
You have a nested for-loop. So for each value of original (3) it will loop through all values of web (3), which is 9 loops total.
In 3 cases it doesn't match and therefore outputs 3 times.
I think this is what you want. I use Linq to grab the primary keys, and then I use Except to do original - web. By the way, you can use == instead of Equals with strings in C# because C# does a value comparison as opposed to a reference comparison.
List<string[]> original = new List<string[]>
{
new string[3] { "a", "2", "3" },
new string[3] { "x", "2", "3" },
new string[3] { "c", "2", "3" }
};
List<string[]> web = new List<string[]>
{
new string[3] { "a", "2", "3" },
new string[3] { "b", "2", "3" },
new string[3] { "c", "2", "3" }
};
var originalPrimaryKeys = original.Select(o => o[0]);
var webPrimaryKeys = web.Select(o => o[0]);
List<string> differences = originalPrimaryKeys.Except(webPrimaryKeys).ToList();
Console.WriteLine("The number of differences is {0}", differences.Count);
foreach (string diff in differences)
{
Console.WriteLine(diff);
}
And here it is without Linq:
var differences = new List<string>();
for (int i = 0; i < original.Count; i++)
{
bool found = false;
for (int j = 0; j < web.Count; j++)
{
if (original[i][0] == web[j][0])
{
found = true;
}
}
if (!found)
{
differences.Add(original[i][0]);
}
}
To answer your question: It is a nested for loop as stated in JanR's answer. This approach will make you reiterate to your web count 9 times, thus listing your mismatched key three times.
What could be a better way to do is this:
//Check for originals not introduced in web.
if(original.Count > web.Count)
{
for(int y = web.Count; y < original.Count; y++)
{
differences.Add(original[y][0]);
}
}
//Check if Web has value, if not, everything else is done on the first for loop
if(web.Count > 0)
{
for(int i = 0; i < original.Count; i++)
{
if(!original[i][0].Equals(web[i][0]))
differences.Add(original[i][0]);
}
}
Also, the output is in a for loop, when you just need one result, the length of the mismatch. You can do that without a loop.
Console.WriteLine("SIZE " + differences.Count);
This is, of course to make it kinda simpler if you're not used to using LINQ statements, but if you can do so with LINQ, then by all means, use LINQ as it's more efficient.
You can get the difference by using Except extension method like this:
var originalDic = original.ToDictionary(arr => arr.First());
var webDic = web.ToDictionary(arr => arr.First());
var differences =
originalDic
.Except(webDic, kvp => kvp.Key)
.Select(kvp => kvp.Value)
.ToList();
The trick here is to first convert your original and web lists into a Dictionary using the first element of each array as key and then perform Except.

Categories

Resources