C# deserialize JSON without custom class into Dictionary or DataTable - c#

from an API I get a json like this:
66 results of an player with somewhat 31 attributes containing single values or an array of values.
{"api":
{"results":66,
"players":
[{
"player_id":10,
"player_name":"Gustavo Ferrareis",
... (some 31 stats)
"shots":{
"total":13,
"on":2
},
...
},
"player_id":21,
...
}]
}
And I wanted to know if there's a way to deserialize the collection of players into an Dictionary or better DataTable with all 31 attributes without a custom player class or accessing every attribute individually?
So far I tried accessing the players list by:
var data = JObject.Parse(json);
foreach (var field in data)
{
var data2 = JObject.Parse(field.Value.ToString());
foreach (var field2 in data2)
{
if (field2.Key.ToString() == "players")
{
dynamic array2 = JsonConvert.DeserializeObject(field2.Value.ToString());
foreach (var field3 in array2)
Console.WriteLine("Player_id: " + field3.player_id.ToString() + " - Player_name: " + field3.player_name.ToString());
}
}
}
which returns
Player_id: 10 - Player_name: Gustavo Ferrareis
Player_id: 22 - Player_name: GetĂșlio
Player_id: 22 - Player_name: GetĂșlio
I imagine something like:
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (var player in array2)
dict.Add(player.Key(), player.Value());
The answer can't be that I have to make an custom player class and then use that?
Open for any advice.
Thank you.

You can use Newtonsoft.Json.Linq and get the required result as shown below:
var jObject = JObject.Parse(jsonFromAPI)["api"];
var formattedPlayers = jObject["Players"].Children()
.Select(p => $"Player_id: {p["player_id"]} - Player_name: {p["player_name"]}");
or if you wanted dictionary, then use below:
var playersDictionary = jObject["Players"].Children().Select(p => new {player_id = p["player_id"], player_name = p["player_name"]}).ToDictionary(x => x.player_id, v => v.player_name);
If you want to display all properties of Players, then you need to run loop something like below:
var allPlayerDetails = new List<Dictionary<string, object>>();
foreach (JObject player in jObject["Players"].Children())
{
var playerDictionary = player.Properties()
.ToDictionary<JProperty, string, object>(property => property.Name, property => property.Value);
allPlayerDetails.Add(playerDictionary);
}
for (var index = 0; index < allPlayerDetails.Count; index++)
{
var playerDictionary = allPlayerDetails[index];
Console.WriteLine(Environment.NewLine);
Console.WriteLine(string.Format("Printing Player# {0}", index));
foreach (var d in playerDictionary)
{
Console.WriteLine(d.Key + " - " + d.Value);
}
}
If you want to convert to DataTable from list of players, then you can do something like below:
DataTable dt = new DataTable();
foreach (var column in allPlayerDetails.SelectMany(p => p.Keys).Select(k => k.Trim()).Distinct())
{
dt.Columns.Add(new DataColumn(column));
}
foreach (var details in allPlayerDetails)
{
var dr = dt.NewRow();
foreach (DataColumn dc in dt.Columns)
{
dr[dc.ColumnName] = details.ContainsKey(dc.ColumnName) ? details[dc.ColumnName] : null;
}
dt.Rows.Add(dr);
}
Fiddler can be found here.

You could parse into IEnumerable<string> like this:
IEnumerable<string> = JObject.Parse(json)["players"]
.Children()
.Select(jo => $"Player_id: {jo["player_id"]} - Player_name: {jo["player_name"]});
A similar approach would work for Dictionary using ToDictionary instead of Select, but it depends on what you consider key and value.

Here is the single line code to get the playerid and playername to List or Dictionary
//To List
var resultToList = JObject.Parse(jsonstring)["api"]["players"]
.Select(p => (p["player_id"].ToString(), p["player_name"].ToString()))
.ToList();
//To Dictionary
var resultToDict = JObject.Parse(jsonstring)["api"]["players"]
.Select(p => (p["player_id"].ToString(), p["player_name"].ToString()))
.ToDictionary(x=>x.Item1, y=>y.Item2);

Related

How to concat values with same key from JSON Array

I'm working on a project where I want to build tokens from a JSON Array.
//Data fed to the system
{"Fruits":[{"Number":"111", "Name":"Apple"}, {"Number":"112", "Name":"Orange"},{"Number":"113", "Name":"Peach"}]}
//serializes the http content to a string
string result = Request.Content.ReadAsStringAsync().Result;
//deserializes result
Dictionary<string, dynamic> data = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(result);
//builds custom tokens
var customTokens = new Dictionary<string, object>();
foreach (var dataField in data)
{
if (dataField.Value is JArray)
{
string nameValue = "";
foreach (JObject content in dataField.Value.Children<JObject>())
{
foreach (JProperty prop in content.Properties())
{
nameValue += prop.Name.ToString() + " : " + prop.Value.ToString();
}
}
customTokens.Add($"{dataField.Key}", nameValue);
}
}
The above code managed to create token $Fruits.
But i also want to achieve token $Number and $Name, where values of each token is from the concatenated values of same key. Example, If I use the "$Number", it will be replaced by 111, 112, 113 and If I use the $Name, it will be replaced by Apple, Orange, Peach.
Also, I'm not using any strongly type models as I don't know what data will be fed to the system.
Any help?
There are a few minor changes to your code to achieve this. First make your dictionary look like this:
var customTokens = new Dictionary<string, List<string>>();
Then, when you loop over all the properties in the array, check if the property has been added, and if not add it.
foreach (JProperty prop in content.Properties())
{
if(customTokens.ContainsKey(prop.Name))
{
customTokens[prop.Name].Add(prop.Value.ToString());
}
else
{
customTokens.Add(prop.Name, new List<string> { prop.Value.ToString() });
}
}
At the end you have a dictionary where the key is the property name and the value is a List<string> - this can be concatenated together:
foreach(var item in customTokens)
{
Console.WriteLine(item.Key + ":" + String.Join(",", item.Value));
}
Or, if you really want it in a dictionary of concatenated strings just do this
var finalResult = customTokens.ToDictionary(k => k.Key, v => String.Format(",",v.Value));
Note you'll need to add using System.Linq to the top of your file to use ToDictionary
Final test code:
var result = "{ \"Fruits\":[{\"Number\":\"111\", \"Name\":\"Apple\"}, {\"Number\":\"112\", \"Name\":\"Orange\"},{\"Number\":\"113\", \"Name\":\"Peach\"}]}";
Dictionary<string, dynamic> data = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(result);
var customTokens = new Dictionary<string, List<string>>();
foreach (var dataField in data)
{
if (dataField.Value is JArray)
{
foreach (JObject content in dataField.Value.Children<JObject>())
{
foreach (JProperty prop in content.Properties())
{
if(customTokens.ContainsKey(prop.Name))
{
customTokens[prop.Name].Add(prop.Value.ToString());
}
else
{
customTokens.Add(prop.Name, new List<string> { prop.Value.ToString() });
}
}
}
foreach(var item in customTokens)
{
Console.WriteLine(item.Key + ":" + String.Join(",", item.Value));
}
}
}

Convert DataTable into a Dictionary using Linq/Lambda

I have a DataTable that I would like to convert into dictionary in C# for my project. I can use the traditional way of programming to achieve the goal but it is not as elegant as using linq/lambda. I tried to use Lambda but I got stuck in how to flatten multiple rows into 1.
I have a mock DataTable for testing purpose.
static DataTable GetData()
{
DataTable table = new DataTable();
table.Columns.Add("Field1", typeof(string));
table.Columns.Add("Field2", typeof(string));
table.Rows.Add("A", "A1");
table.Rows.Add("A", "A2");
table.Rows.Add("B", "B1");
table.Rows.Add("A", "A3");
table.Rows.Add("C", "C1");
table.Rows.Add("D", "D1");
table.Rows.Add("A", "A5");
return table;
}
My traditional way to convert it to Dictionary is:
Dictionary<string, ArrayList> t = new Dictionary<string, ArrayList>();
foreach (DataRow r in GetData().Rows)
{
string k = (string)r["Field1"];
string v = (string)r["Field2"];
if (!t.Keys.Contains(r["Field1"]))
{
t.Add(k, new ArrayList());
}
if (t.Values == null)
{
t[k] = new ArrayList();
}
t[k].Add(v);
}
How do I achieve the same thing with Linq?
I have tried:
var res = GetData()
.AsEnumerable()
.GroupBy(row => row.Field<string>("Field1"))
.Select(grp => grp.First());
This only gives me the first occurrence of the item. I am stuck.
Please help.
Actually, you don't want to convert it to a Dictionary, but to a Lookup. Here's an example:
var lookup = GetData().AsEnumerable()
.ToLookup(r => r.Field<string>("Field1"), r => r.Field<string>("Field2"));
foreach (var grouping in lookup)
{
Console.WriteLine(grouping.Key + ": " + String.Join(", ", grouping));
}
Output:
A: A1, A2, A3, A5
B: B1
C: C1
D: D1
Get Data from Datatable as Dictionary without Linq/Lambda
DataTable dataTable = GetData();
var data = new List<Dictionary<string, object>>();
foreach (DataRow dataTableRow in dataTable.Rows)
{
var dic = new Dictionary<string, object>();
foreach (DataColumn tableColumn in dataTable.Columns)
{
dic.Add(tableColumn.ColumnName, dataTableRow[tableColumn]);
}
data.Add(dic);
}
you can get a Collection:
var res = GetData()
.AsEnumerable()
.Select(grp => new KeyValuePair<string, string>(grp[0].ToString(), grp[1].ToString()));

Convert DataTable to LINQ Anonymous Type

I want a function which takes in a datatable & returns a List (object is not DataRow)
Eg. :
I know I can do this (but this requires column names to be known) :
// Datatable dt = Filled from a Database query & has 3 columns Code,Description & ShortCode
List<object> rtn = new List<object>();
var x = from vals in dt.Select()
select new
{
Code = vals["Code"],
Description = vals["Description"],
ShortCode = vals["ShortCode"],
};
rtn.AddRange(x)
return rtn;
What i want is a generic version so that i can pass in any datatable & it will generate based on column names in the datatable.
Since the property names are not known at compile time and you want to use the data for JSON serialization, you can use the following to create a list of dictionary. If you use Newtonsoft JSON, then the serialization takes care of converting the key value pairs in a JSON object format.
IEnumerable<Dictionary<string,object>> result = dt.Select().Select(x => x.ItemArray.Select((a, i) => new { Name = dt.Columns[i].ColumnName, Value = a })
.ToDictionary(a => a.Name, a => a.Value));
In order to dynamically create properties so as to treat different dataTables with different set of Columns, we can use the System.Dynamic.ExpandoObject. It basically implements, IDictionary <string,object>. The format, which can easily be converted to JSON.
int colCount = dt.Columns.Count;
foreach (DataRow dr in dt.Rows)
{
dynamic objExpando = new System.Dynamic.ExpandoObject();
var obj = objExpando as IDictionary<string, object>;
for (int i = 0; i < colCount; i++)
{
string key = dr.Table.Columns[i].ColumnName.ToString();
string val = dr[key].ToString();
obj[key] = val;
}
rtn.Add(obj);
}
String json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(rtn);
You can use the following generic function:-
private static List<T> ConvertDataTable<T>(DataTable dt)
{
List<T> data = newList<T>();
foreach (DataRowrow in dt.Rows)
{
Titem = GetItem<T>(row);
data.Add(item);
}
return data;
}
private static TGetItem<T>(DataRow dr)
{
Type temp = typeof(T);
T obj =Activator.CreateInstance<T>();
foreach (DataColumncolumn in dr.Table.Columns)
{
foreach (PropertyInfopro in temp.GetProperties())
{
if (pro.Name == column.ColumnName)
pro.SetValue(obj,dr[column.ColumnName], null);
else
continue;
}
}
return obj;
}
Please check my article, which has complete demonstration on how to use this generic method.
Here is the original question:
// Datatable dt = Filled from a Database query & has 3 columns Code,Description & ShortCode
List<object> rtn = new List<object>();
var x = from vals in dt.Select()
select new
{
Code = vals["Code"],
Description = vals["Description"],
ShortCode = vals["ShortCode"],
};
rtn.AddRange(x)
return rtn;
Just replace with
List<object> rtn = JsonConvert.DeserializeObject<List<object>>(JsonConvert.SerializeObject(dt));
You will have the provide the anonymous object as a parameter and use json/xml serialization:
protected static List<T> ToAnonymousCollection<T>(DataTable dt, T anonymousObject)
{
List<DataColumn> dataColumns = dt.Columns.OfType<DataColumn>().ToList();
return dt.Rows.OfType<DataRow>().Select(dr =>
{
Dictionary<string, object> dict = new Dictionary<string, object>();
dataColumns.Each(dc => dict.Add(dc.ColumnName, dr[dc]));
return JsonConvert.DeserializeAnonymousType(JsonConvert.SerializeObject(dict), anonymousObject);
}).ToList();
}
Usage:
var anonymousCollection = ToAnonymousCollection(dt, new { Code = [ColumnTypeValue, eg. 0], Description = [ColumnTypeValue, eg. string.Empty], ShortCode = Code=[ColumnTypeValue, eg. 0] })

Comparing several Datatables with same structure

I'm creating Datatables from .csv files. This part actually works. My current issue is the following one:
I have to compare two or more Datatable's with the same structure. So
Datatable1:
KeyColumn, ValueColumn
KeyA, ValueA
KeyB, ValueB
KeyC, ValueC
Datatable2:
KeyColumn, ValueColumn
KeyB, ValueB
KeyC, ValueC
KeyD, ValueD
And this should end up like this:
ResultDatatable:
KeyColumn, ValueColumn (of DT1), ValueColumn (of DT2)
KeyA, ValueA
KeyB, ValueB (of DT1), ValueB (of DT2)
KeyC, ValueC (of DT1), ValueC (of DT2)
KeyD, ValueD
I can't even manage to insert the Data of the first Datatable because of different ColumnNames. Another problem is, that the Datatables own the same ColumnNames, so I can't add those to the ResultDatatable.
I have tried many ways and end up with no solution. Any ideas how to address this problem?
Edit:
The solution with Dictionaries was too sophisticated, so I continued trying to solve it with the Datatables. The source of the problem was something very unexpected.
The attempt to rename a column name to something, which contains a simple dot ('.') results with losing all data in that column.
e.g. If you have Datatable dt:
PrimaryColumn, ValueColumn
KeyA1, KeyB1
KeyA2, KeyB2
After dt.Columns[ValueColumn].ColumnName = "Value.Column"; You will lose any data in that column. I will ask MS, if this is desired or if it is a Bug in the .NET-Framework. Here is my final Code (C#). I have List<string>keys which will remain in the resultTable. and List<string>values which will be added for every Table that should be compared.
private DataTable CompareTables(List<AnalyseFile> files, Query query, List<string> keys, List<string> values) {
// Add first table completely to resultTable
DataTable resultTable =
files[0].GetDataTable(false, query.Header, query.Startstring, query.Endstring, query.Key).Copy();
foreach (string value in values) {
resultTable.Columns[value].ColumnName = "(" + files[0].getFileNameWithoutExtension() + ") " + value;
}
// Set primary keys
resultTable.PrimaryKey = keys.Select(key => resultTable.Columns[key]).ToArray();
// process remaining tables
for (int i = 1; i < files.Count; i++) {
DataTable currentTable = files[i].GetDataTable(false, query.Header, query.Startstring, query.Endstring, query.Key);
// Add value-columns to the resultTable
foreach (string value in values) {
resultTable.Columns.Add("(" + files[i].getFileNameWithoutExtension() + ") " + value);
}
// Set again primary keys
currentTable.PrimaryKey = keys.Select(key => currentTable.Columns[key]).ToArray();
// populate common Rows
foreach (DataRow dataRow in resultTable.Rows) {
foreach (DataRow row in currentTable.Rows) {
foreach (string key in keys) {
if (dataRow[key].ToString().Equals(row[key].ToString())) {
foreach (string value in values) {
string colname = "(" + files[i].getFileNameWithoutExtension() + ") " + value;
dataRow[colname] = row[value];
}
}
}
}
}
// Get all Rows, which do not exist in resultTable yet
IEnumerable<string> isNotinDT =
currentTable.AsEnumerable()
.Select(row => row.Field<string>(keys[0]))
.Except(resultTable.AsEnumerable().Select(row => row.Field<string>(keys[0])));
// Add all the non existing rows to resulTable
foreach (string row in isNotinDT) {
DataRow currentRow = currentTable.Rows.Find(row);
DataRow dRow = resultTable.NewRow();
foreach (string key in keys) {
dRow[key] = currentRow[key];
}
foreach (string value in values) {
dRow["(" + files[i].getFileNameWithoutExtension() + ") " + value] = currentRow[value];
}
resultTable.Rows.Add(dRow);
}
}
return resultTable;
}
Any improvements are Welcome!
Ok Here is an example of my version using the dictionaries.
Fiddle: http://dotnetfiddle.net/AljK9J
//Setup Sample Data
var data1 = new Dictionary<string, string>();
data1.Add("KeyA", "ValueA");
data1.Add("KeyB", "ValueB");
data1.Add("KeyC", "ValueC");
var data2 = new Dictionary<string, string>();
data2.Add("KeyB", "ValueB");
data2.Add("KeyC", "ValueC");
data2.Add("KeyD", "ValueD");
//Second DataType in the Dictionary could be something other than a Tuple
var result = new Dictionary<string, Tuple<string, string>>();
//Fill in for items existing only in data1 and in both data1 and data2
foreach(var item in data1)
{
result.Add(item.Key, new Tuple<string, string>(item.Value, data2.FirstOrDefault(x => x.Key == item.Key).Value));
}
//Fill in remaining items that exist only in data2
foreach(var item in data2.Where(d2 => !result.Any(x => x.Key == d2.Key )))
{
result.Add(item.Key, new Tuple<string, string>(null, item.Value));
}
//Demonstrating how to access the data
var formattedOutput = result.Select(x => string.Format("{0}, {1} (of D1), {2} (of D2)", x.Key, x.Value.Item1 ?? "NoValue", x.Value.Item2 ?? "NoValue"));
foreach(var line in formattedOutput)
{
Console.WriteLine(line);
}

querying existing ListView items with LINQ

The ListView I have populates through these loops resulting in four columns being filled
// Create a ResXResourceReader
ResXResourceReader rdr0 = new ResXResourceReader(textPath1.Text + ".resx");
ResXResourceReader rdr1 = new ResXResourceReader(textPath1.Text + ".es.resx");
ResXResourceReader rdr2 = new ResXResourceReader(textPath1.Text + ".fr.resx");
foreach (DictionaryEntry d in rdr0)
{
TransResource x = new TransResource();
x.id = d.Key.ToString();
x.en = d.Value.ToString();
resources.Add(x.id, x);
}
foreach (DictionaryEntry d in rdr1)
{
TransResource x = resources[d.Key.ToString()];
x.fr = d.Value.ToString();
}
foreach (DictionaryEntry d in rdr2)
{
TransResource x = resources[d.Key.ToString()];
x.es = d.Value.ToString();
}
foreach (TransResource x in resources.Values)
{
string[] row = { x.id, x.en, x.fr, x.es };
var listViewItem = new ListViewItem(row);
listResx.Items.Add(listViewItem);
}
What I want to do is query all of the results in this ListView against what is entered in textboxQuery. If any field in the entire listview contains the string from textboxQuery I want it to be displayed in a new listview (lets say listviewQueryresult). I've had many failed attempts at this but I know it is possible through LINQ.
Because ListView.Items implements IEnumerable, but does not implement IEnumerable<T> you have to cast it to IEnumerable<ListViewItem> first, to query it using LINQ to Objects:
var results = listResx.Items.Cast<ListViewItem>()
.Where(x => YourPredicate(x));
If any field in the entire listview contains the string from
textboxQuery I want it to then be displayed in a new listview (lets
say listviewQueryresult)
For that, the predicate would be just:
var results = listResx.Items.Cast<ListViewItem>()
.Where(x => x.Text.Contains(textboxQuery));

Categories

Resources