I want to generically flatten some json so I can convert to a datatable and bind to a datagrid using c#
What is the best way of doign it, bearing in mind I dont know how many levels I am going down?
e.g.
{
"appointmentid": 4,
"policyid": 1,
"guid": "00000000-0000-0000-0000-000000000000",
"number": "1234567890",
"ampm": "false",
"date": "2015-09-08T00:00:00",
"vehicle": {
"id": 1,
"guid": "00000000-0000-0000-0000-000000000000",
"make": null,
"model": null
},
"installer": {
"installerid": "1",
"name": "Installer 1",
"contact": "qwerty",
"qascore": "0",
"address1": "qwerty",
"address2": "qwerty",
"address3": null,
"address4": null,
"city": "qwertyu",
"county": "qwertyu",
"postcode": "asdfghj",
"country": "GB",
"email": "asdfghj",
"web": "asdfghjk",
"archived": false
},
"installations": [
{
"installationid": 6,
"installationstatus": {
"installationstatusid": 4,
"installationstatus": "FAIL"
},
"isactive": true
},
{
"installationid": 7,
"installationstatus": {
"installationstatusid": 1,
"installationstatus": "NEW"
},
"isactive": false
}
],
"archived": false
}
i would like to extend this (I suppose I could iterate over the datatable on I had converted it) rather than installations.1.installationid, i would get installationid1.
as I'm going to be displaying the resulting datatable in a grid I would like to keep the column names friendly.
You can use Json.Net's LINQ-to-JSON API to parse the data into a JToken structure. From there, you can use a recursive helper method to walk the structure and flatten it to a Dictionary<string, object> where the keys are the "path" to each value from the original JSON. I would write it something like this:
public class JsonHelper
{
public static Dictionary<string, object> DeserializeAndFlatten(string json)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
JToken token = JToken.Parse(json);
FillDictionaryFromJToken(dict, token, "");
return dict;
}
private static void FillDictionaryFromJToken(Dictionary<string, object> dict, JToken token, string prefix)
{
switch (token.Type)
{
case JTokenType.Object:
foreach (JProperty prop in token.Children<JProperty>())
{
FillDictionaryFromJToken(dict, prop.Value, Join(prefix, prop.Name));
}
break;
case JTokenType.Array:
int index = 0;
foreach (JToken value in token.Children())
{
FillDictionaryFromJToken(dict, value, Join(prefix, index.ToString()));
index++;
}
break;
default:
dict.Add(prefix, ((JValue)token).Value);
break;
}
}
private static string Join(string prefix, string name)
{
return (string.IsNullOrEmpty(prefix) ? name : prefix + "." + name);
}
}
Using this DeserializeAndFlatten method with your JSON you would end up with key-value pairs like this:
appointmentid: 4
policyid: 1
guid: 00000000-0000-0000-0000-000000000000
number: 1234567890
ampm: false
date: 9/8/2015 12:00:00 AM
vehicle.id: 1
vehicle.guid: 00000000-0000-0000-0000-000000000000
vehicle.make:
vehicle.model:
installer.installerid: 1
installer.name: Installer 1
installer.contact: qwerty
installer.qascore: 0
installer.address1: qwerty
installer.address2: qwerty
installer.address3:
installer.address4:
installer.city: qwertyu
installer.county: qwertyu
installer.postcode: asdfghj
installer.country: GB
installer.email: asdfghj
installer.web: asdfghjk
installer.archived: False
installations.0.installationid: 6
installations.0.installationstatus.installationstatusid: 4
installations.0.installationstatus.installationstatus: FAIL
installations.0.isactive: True
installations.1.installationid: 7
installations.1.installationstatus.installationstatusid: 1
installations.1.installationstatus.installationstatus: NEW
installations.1.isactive: False
archived: False
If you're looking to make the keys more human friendly, you could use a little string manipulation to cut them down. Maybe something like this:
var dict = JsonHelper.DeserializeAndFlatten(json);
foreach (var kvp in dict)
{
int i = kvp.Key.LastIndexOf(".");
string key = (i > -1 ? kvp.Key.Substring(i + 1) : kvp.Key);
Match m = Regex.Match(kvp.Key, #"\.([0-9]+)\.");
if (m.Success) key += m.Groups[1].Value;
Console.WriteLine(key + ": " + kvp.Value);
}
That would give you this output instead:
appointmentid: 4
policyid: 1
guid: 00000000-0000-0000-0000-000000000000
number: 1234567890
ampm: false
date: 9/8/2015 12:00:00 AM
id: 1
guid: 00000000-0000-0000-0000-000000000000
make:
model:
installerid: 1
name: Installer 1
contact: qwerty
qascore: 0
address1: qwerty
address2: qwerty
address3:
address4:
city: qwertyu
county: qwertyu
postcode: asdfghj
country: GB
email: asdfghj
web: asdfghjk
archived: False
installationid0: 6
installationstatusid0: 4
installationstatus0: FAIL
isactive0: True
installationid1: 7
installationstatusid1: 1
installationstatus1: NEW
isactive1: False
archived: False
But note, with this arrangement, you have lost some context: for example, you can see that there are now two identical archived keys, whereas in the original JSON they were distinct because they appeared in different parts of the hierarchy (installer.archived vs. archived). You will need to figure out how to deal with that problem on your own.
Fiddle: https://dotnetfiddle.net/gzhWHk
Using the library Json.Net You could use the JSONPath $..* to get all members of the JSON structure and filter out the ones with no children to skip the container properties.
e.g.
var schemaObject = JObject.Parse(schema);
var values = schemaObject
.SelectTokens("$..*")
.Where(t => !t.HasValues)
.ToDictionary(t => t.Path, t => t.ToString());
Another variant using Newtonsoft's Json.NET LINQ to JSON for object at root (the same can be done with JArray also):
var flattened = JObject.Parse(json)
.Descendants()
.OfType<JValue>()
.ToDictionary(jv => jv.Path, jv => jv.ToString())
Looked also for a solution and that did the job for me. Used new C# 7 Tupels for the result set. If anyone have another lightweight solution i'm interested:-)
async Task Main()
{
var jsonUsers = await new HttpClient().GetStringAsync(#"https://jsonplaceholder.typicode.com/users");
foreach (var flattedChild in GetFlatJsonChilds(JToken.Parse(jsonUsers)))
Console.WriteLine($"{flattedChild.path}: {flattedChild.value}");
}
IEnumerable<(string path, string value)> GetFlatJsonChilds(JToken token)
{
foreach (var child in token.Children())
{
if(token.Type != JTokenType.Array &&token.Children().First().Type != JTokenType.Property && !child.Children().Any())
yield return (child.Path, child.ToString());
foreach(var childChild in GetFlatJsonChilds(child))
yield return childChild;
}
}
The Result for https://jsonplaceholder.typicode.com/users :
[0].id: 1
[0].name: Leanne Graham
[0].username: Bret
[0].email: Sincere#april.biz
[0].address.street: Kulas Light
[0].address.suite: Apt. 556
[0].address.city: Gwenborough
[0].address.zipcode: 92998-3874
[0].address.geo.lat: -37.3159
[0].address.geo.lng: 81.1496
[0].phone: 1-770-736-8031 x56442
[0].website: hildegard.org
[0].company.name: Romaguera-Crona
[0].company.catchPhrase: Multi-layered client-server neural-net
[0].company.bs: harness real-time e-markets
[1].id: 2
[1].name: Ervin Howell
[1].username: Antonette
[1].email: Shanna#melissa.tv
[1].address.street: Victor Plains
[1].address.suite: Suite 879
[1].address.city: Wisokyburgh
[1].address.zipcode: 90566-7771
[1].address.geo.lat: -43.9509
[1].address.geo.lng: -34.4618
[1].phone: 010-692-6593 x09125
[1].website: anastasia.net
[1].company.name: Deckow-Crist
[1].company.catchPhrase: Proactive didactic contingency
[1].company.bs: synergize scalable supply-chains
[2].id: 3
[2].name: Clementine Bauch
[2].username: Samantha
[2].email: Nathan#yesenia.net
[2].address.street: Douglas Extension
[2].address.suite: Suite 847
[2].address.city: McKenziehaven
[2].address.zipcode: 59590-4157
[2].address.geo.lat: -68.6102
[2].address.geo.lng: -47.0653
[2].phone: 1-463-123-4447
[2].website: ramiro.info
[2].company.name: Romaguera-Jacobson
[2].company.catchPhrase: Face to face bifurcated interface
[2].company.bs: e-enable strategic applications
[3].id: 4
[3].name: Patricia Lebsack
[3].username: Karianne
[3].email: Julianne.OConner#kory.org
[3].address.street: Hoeger Mall
[3].address.suite: Apt. 692
[3].address.city: South Elvis
[3].address.zipcode: 53919-4257
[3].address.geo.lat: 29.4572
[3].address.geo.lng: -164.2990
[3].phone: 493-170-9623 x156
[3].website: kale.biz
[3].company.name: Robel-Corkery
[3].company.catchPhrase: Multi-tiered zero tolerance productivity
[3].company.bs: transition cutting-edge web services
[4].id: 5
[4].name: Chelsey Dietrich
[4].username: Kamren
[4].email: Lucio_Hettinger#annie.ca
[4].address.street: Skiles Walks
[4].address.suite: Suite 351
[4].address.city: Roscoeview
[4].address.zipcode: 33263
[4].address.geo.lat: -31.8129
[4].address.geo.lng: 62.5342
[4].phone: (254)954-1289
[4].website: demarco.info
[4].company.name: Keebler LLC
[4].company.catchPhrase: User-centric fault-tolerant solution
[4].company.bs: revolutionize end-to-end systems
[5].id: 6
[5].name: Mrs. Dennis Schulist
[5].username: Leopoldo_Corkery
[5].email: Karley_Dach#jasper.info
[5].address.street: Norberto Crossing
[5].address.suite: Apt. 950
[5].address.city: South Christy
[5].address.zipcode: 23505-1337
[5].address.geo.lat: -71.4197
[5].address.geo.lng: 71.7478
[5].phone: 1-477-935-8478 x6430
[5].website: ola.org
[5].company.name: Considine-Lockman
[5].company.catchPhrase: Synchronised bottom-line interface
[5].company.bs: e-enable innovative applications
[6].id: 7
[6].name: Kurtis Weissnat
[6].username: Elwyn.Skiles
[6].email: Telly.Hoeger#billy.biz
[6].address.street: Rex Trail
[6].address.suite: Suite 280
[6].address.city: Howemouth
[6].address.zipcode: 58804-1099
[6].address.geo.lat: 24.8918
[6].address.geo.lng: 21.8984
[6].phone: 210.067.6132
[6].website: elvis.io
[6].company.name: Johns Group
[6].company.catchPhrase: Configurable multimedia task-force
[6].company.bs: generate enterprise e-tailers
[7].id: 8
[7].name: Nicholas Runolfsdottir V
[7].username: Maxime_Nienow
[7].email: Sherwood#rosamond.me
[7].address.street: Ellsworth Summit
[7].address.suite: Suite 729
[7].address.city: Aliyaview
[7].address.zipcode: 45169
[7].address.geo.lat: -14.3990
[7].address.geo.lng: -120.7677
[7].phone: 586.493.6943 x140
[7].website: jacynthe.com
[7].company.name: Abernathy Group
[7].company.catchPhrase: Implemented secondary concept
[7].company.bs: e-enable extensible e-tailers
[8].id: 9
[8].name: Glenna Reichert
[8].username: Delphine
[8].email: Chaim_McDermott#dana.io
[8].address.street: Dayna Park
[8].address.suite: Suite 449
[8].address.city: Bartholomebury
[8].address.zipcode: 76495-3109
[8].address.geo.lat: 24.6463
[8].address.geo.lng: -168.8889
[8].phone: (775)976-6794 x41206
[8].website: conrad.com
[8].company.name: Yost and Sons
[8].company.catchPhrase: Switchable contextually-based project
[8].company.bs: aggregate real-time technologies
[9].id: 10
[9].name: Clementina DuBuque
[9].username: Moriah.Stanton
[9].email: Rey.Padberg#karina.biz
[9].address.street: Kattie Turnpike
[9].address.suite: Suite 198
[9].address.city: Lebsackbury
[9].address.zipcode: 31428-2261
[9].address.geo.lat: -38.2386
[9].address.geo.lng: 57.2232
[9].phone: 024-648-3804
[9].website: ambrose.net
[9].company.name: Hoeger LLC
[9].company.catchPhrase: Centralized empowering task-force
[9].company.bs: target end-to-end models
Deserialize, then LINQ select to flatten. I'm guessing, since you haven't stated it, that you want all the appointment and installer information on the same record as the specific installation?
My initial idea would be to leverage dynamics so that you can avoid having to put in static schemas for your JSON. If you already have static types that can act as JSON schemas, then you can avoid dynamics (and all that it entails). Here's an example class - using JSON.NET - to illustrate what I'm thinking:
public class DeserializeAndFlatten
{
public dynamic ParseJson()
{
var appointment = JObject.Parse(JsonData.JSON_TO_PARSE); // <-- replace the constant w/ the real JSON...
// this is where you flatten it all out!
// not going to put all the fields in, that would kill the example, LOL
var installations = appointment["installations"].Select(installation => new
{
appointmentId = appointment["appointmentid"],
policyId = appointment["policyid"],
vehicleId = appointment["vehicle"]["id"],
vehicleMake = appointment["vehicle"]["make"],
vehicleModel = appointment["vehicle"]["model"],
installerId = appointment["installer"]["installerid"],
installerName = appointment["installer"]["name"],
installationId = installation["installationid"],
installationStatus = installation["installationstatus"]["installationstatus"],
installationStatusId = installation["installationstatus"]["installationstatusid"],
}).ToList();
return installations;
}
}
You can test the code:
static void Main(string[] args)
{
var jsonParser = new DeserializeAndFlatten();
var installations = jsonParser.ParseJson();
// FYI we get back a dynamic listing,
// so intellisense wont work...
foreach (var installation in installations)
{
Console.WriteLine($"appointmentId: {installation.appointmentId}");
Console.WriteLine($"installer: {installation.installerName}");
Console.WriteLine($"installation id: {installation.installationId}");
Console.WriteLine($"status: {installation.installationStatus}");
Console.WriteLine();
}
Console.ReadLine();
}
For those who need same in F#:
module JsonFlatten =
let Join prefix name =
if String.IsNullOrEmpty(prefix) then name else prefix + "." + name
let rec FillDictionaryFromJToken (dict:Dictionary<string, string>) (token:JToken) (prefix:string) =
match token.Type with
| JTokenType.Object ->
for prop in token.Children<JProperty>() do
FillDictionaryFromJToken dict prop.Value (Join prefix prop.Name)
| JTokenType.Array ->
let mutable index = 0
for value in token.Children() do
FillDictionaryFromJToken dict value (Join prefix (index.ToString()))
index <- index + 1
| _ ->
dict.Add(prefix, sprintf "%A" (token :?> JValue).Value)
let DeserializeAndFlatten(json:string) =
let dict = Dictionary<string, string>()
let token = JToken.Parse(json);
FillDictionaryFromJToken dict token ""
dict
Here is one another way to flatten JSON / convert to DataTable using Cinchoo ETL
Flatten JSON:
using (var r = new ChoJSONReader("*** JSON file path ***"))
{
foreach (var rec in r.Select(f => f.Flatten()))
Console.WriteLine(rec.Dump());
}
JSON to DataTable:
using (var r = new ChoJSONReader("*** JSON file path ***"))
{
var dt = r.AsDataTable();
Console.WriteLine(dt.DumpAsJson());
}
I needed to load some JSON into a key/value list today, and decided to use the Microsoft configuration in .NET from "Microsoft.Extensions.Configuration":
ConfigurationBuilder jsonConfigurationBuilder = new ConfigurationBuilder();
jsonConfigurationBuilder.AddJsonFile(fileName, false, false);
IConfiguration jsonConfiguration = jsonConfigurationBuilder.Build();
Now the JSON is loaded and parset into IConfiguration, and then it is simple to get it into a list of key/value pairs using this method:
public IEnumerable<KeyValuePair<String, Object>> GetConfigurationEnumerator(IConfiguration configuration) {
// Get the configuration child sections into a stack.
Stack<IConfigurationSection> configurationSectionStack = new Stack<IConfigurationSection>();
foreach (IConfigurationSection configurationSection in configuration.GetChildren()) {
configurationSectionStack.Push(configurationSection);
}
// Return a key/value pair for each configuration section, and add additional child sections to the stack.
while (configurationSectionStack.Count > 0) {
// Get the configuration section.
IConfigurationSection configurationSection = configurationSectionStack.Pop();
// eturn a key/value pair.
yield return new KeyValuePair<String, Object>(configurationSection.Path, configurationSection.Value);
// Add the child sections to the stack.
foreach (IConfigurationSection configurationSectionChild in configurationSection.GetChildren()) {
configurationSectionStack.Push(configurationSectionChild);
}
}
} // GetConfigurationEnumerator
And write the result to the console:
foreach (KeyValuePair<String, Object> value in GetConfigurationEnumerator(jsonConfiguration)) {
Console.WriteLine($" {value.Key} == {value.Value}");
}
The result is like: "level0:level1:level2 == this is the value"
Related
This is my problem: I have a nested dictionary (primary keys: orderIds, secondary keys: productIds).
My scheme:
Dictionary<string, Dictionary<string, List<Task>>>
I need to look for the productId and return that value (Task object).
It's usually not the case that a product key appears in several orders. So it's unique.
Here is a json example:
{
"O1": {
"P1": [
{
"Field": "V1"
},
{
"Field": "V7"
}
],
"P2": [
{
"Field": "V2"
},
{
"Field": "V8"
}
]
},
"O2": {
"P1": [
{
"Field": "V3"
},
{
"Field": "V5"
}
],
"P2": [
{
"Field": "V4"
},
{
"Field": "V6"
}
]
}
}
If I look for productId "P5" I want to get...
[{"Field":"V5"}]
The only way I found that runs is...
return base.Values.Single(x => x.ContainsKey(productId))[productId];
//base is the nested Dictionary
But I don't like what I'm doing here. Because I detach the correct dictionary from the collection (values) where the key exists and finally I fetch only the value via key filtering (collection[key]).
That are basically two steps, but I suspect there is an easier way with just one step. - I just can't find this one.
Maybe you can help me out. :)
For performance tuning, use HashSet instead of List.
I also can recommend ISBN: 0321637003 (LINQ to Objects), maybe there is an updated version. But anyway its still very good content.
If you have millions of entries in your dict, you can try PLINQ.
return base.Values.Single(x => x.ContainsKey(productId))[productId];
The Sample json throws an exception, because your productId (P1, P2) is not unique and P5 does not exist.
If I look for productId "P5" I want to get...
[{"Field":"V5"}]
There Is no P5
I am not sure its helping but, here is my sample code. I have put your json into a file and deserialize it.
var lFile = new FileInfo(#"C:\_test\data.json");
using var lReader = lFile.OpenText();
var lJsonStr = lReader.ReadToEnd();
var lDicDic = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, List<ProductId>>>>(lJsonStr);
//var lTest1 = lDicDic.Values.Single(x => x.ContainsKey("P1"))["P1"]; //Not working, P1 is not unique!
var lTest2 = lDicDic["O2"]["P1"];
//contains => "Field": "V3" + "Field": "V5"
var lTest3 = lDicDic
.SelectMany(p => p.Value.Values.SelectMany(qItem => qItem))
.FirstOrDefault(qProducts => qProducts.Field == "V5");
//contains => "Field": "V5"
var lTest4 = lDicDic["O2"]["P1"].Last();
//contains => "Field": "V5"
EDIT:
After your Fiddler code I was able to test it.
public List<MyTask> GetByProductId(string productId)
{
var lProductDict = Tasks.Values.SingleOrDefault(x => x.ContainsKey(productId));
return lProductDict?.GetValueOrDefault(productId);
}
My stomach tells my, your current approach is hard to make any better ;)
If you have a lot of calls on this nasted dict, and its changing not very often it could make sense to refactor the dict from TopDown to BottomUp at the beginning for the processing.
Anyway, If my comments are helpful plz upvote my answere :)
If I were you I would make some testcases with real data.
Dont forget: If you are in DEBUG mode, to enable "Optimize Code" at the Project. In my testcase, I had an AVG of 47ms without and 42ms with optimized code:
You can give PLINQ a try:
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq
Here us my test scenario:
var lStopWatch = new Stopwatch();
lStopWatch.Restart();
var lJobs = new Jobs("F1");
const int TestOrderCount = 1000000;
const int TestAvgCount = 1000;
for (var lIndex = 1; lIndex < TestOrderCount; lIndex++)
{
var lNoStr = lIndex.ToString("D6");
lJobs.Add($"O{lNoStr}", $"P{lNoStr}", $"V{lNoStr}");
}
var GetTimes = new List<long>();
var lRandom = new Random();
var lTestCases = Enumerable
.Range(1, TestAvgCount - 1)
.Select(r => $"P{lRandom.Next(1, TestOrderCount - 1):D6}")
.ToList();
var lSetupTimeMs = lStopWatch.ElapsedMilliseconds;
Debug.WriteLine($"SetupTimeMs: {lSetupTimeMs}");
foreach (var lTestCase in lTestCases)
{
lStopWatch.Restart();
var lTest = lJobs.GetByProductId(lTestCase);
GetTimes.Add(lStopWatch.ElapsedMilliseconds);
}
var lAvg = GetTimes.Sum() / TestAvgCount; //AVG Ms per get
Debug.WriteLine($"AVG: {lAvg}");
I have a variety of input JSON formatted data which all contain a particular key-name terminalSize. This is the only piece I know. The total number of JSON trees or the exact depth of terminalSize inside the JSON tree will forever be an unknown and subject to change.
I'm looking for a C# solution to loop through every child of the JSON string and find terminalSize then fetch the value.
I've tried this with success but it will only work if terminalSize is in the first level of the JSON:
var list = JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(jsonString);
var dict = list.SelectMany(d => d).ToDictionary(p => p.Key, p => p.Value);
var terminal = dict["terminalSize"];
Example 1.
{
"status": "success",
"data": {
"terminalSize": 3766505.46,
"totalTerminalSize": 3766505.46
},
"message": null
}
Example 2.
{
"lastUpdated": 1588020678,
"terminalData": {
"terminalSize": "451679852",
"totalTerminalSize": "2100000000"
},
"terminalValueSeries": {
"8x7": 2.33,
"8x6": 3.73,
"8x5": 4.49,
"8x4": 3.68,
"8x3": 13998,
"8x2": 274936,
"8x1": 5.09
}
}
Example 3.
{
"terminalSize": "492612346.17",
"terminalStatus": "online"
}
You can parse your JSON to a JToken, then use SelectToken with the recursive descent JsonPath operator .. to get the terminalSize anywhere in the JSON:
var terminalSize = (double?) JToken.Parse(json).SelectToken("$..terminalSize");
Fiddle: https://dotnetfiddle.net/5ziYbP
If there might be multiple terminalSize keys in the JSON, for example if you had an array of terminals, you can use SelectTokens instead and put the terminal sizes into a Dictionary keyed by path:
var sizes = JToken.Parse(json4)
.SelectTokens("$..terminalSize")
.ToDictionary(t => t.Path, t => (double)t);
Fiddle: https://dotnetfiddle.net/ivSM88
You could also use linq and filter the JProperty collection based on JProperty.Name. For example
var result = JObject.Parse(jsonString)
.DescendantsAndSelf()
.OfType<JProperty>()
.Single(x=>x.Name.Equals("terminalSize"))
.Value;
You may parse your JSON into JObject, then recursively go through all properties and sub objects to find a terminalSize value. There is no need to deserialize the entire JSON into specific object
var json = JObject.Parse(jsonString);
var result = GetTerminalSize(json);
double GetTerminalSize(JObject input)
{
foreach (var property in input.Properties())
{
if (property.Name == "terminalSize")
return property.Value.Value<double>();
if (property.Value.Type == JTokenType.Object)
return GetTerminalSize((JObject) property.Value);
//not sure, if the is a need to handle an array
if (property.Value.Type == JTokenType.Array)
foreach (var item in (JArray) property.Value)
return GetTerminalSize((JObject) item);
}
return 0;
}
It returns a correct value for all 3 examples
What is the best way to design a DTO for a REST API to get a resource that is semantically a dictionary?
Example: Get the number of employees by age -> should return a set of couples (age, number) of type (int, int).
An easy way should be an array of structures, like:
[
{
age: 30,
number: 3
},
{
age: 31,
number: 4
},
{
age: 32,
number: 5
}
]
but this does not enforces that the key should be unique.
Semantically, I'd rather prefer a dictionary, like:
{
"30": 3,
"31": 4,
"32": 5
}
Is there a way to define such a DTO in C#, so that it can be easily serialized/deserialized?
Your second Json is not valid, it should look like:
{
"30": 3,
"31": 4,
"32": 5
}
And in c# it's just a Dictionary<string, int>
You could easiliy deserialize/serialize it with Json.Net
var result =JsonConvert.DeserializeObject<Dictionary<string, int>>(text);
var serialized = JsonConvert.SerializeObject(
new Dictionary<string, int> { { "1", 2 }, {"3",4}});
As suggested by maksim-simkin in his answer, dictionaries are natively supported by Json.Net.
Here's an example:
var result = JsonConvert.DeserializeObject<Dictionary<int, int>>(text);
var serialized = JsonConvert.SerializeObject(new Dictionary<int, int> { { 30, 3 }, { 31, 4}});
// here serialized == "{\"30\":3,\"31\":4,\"32\":5}"
If it can be useful, this serialization is supported in Nancy through the DynamicDictionary class (in namespace Nancy). In a NancyModule, you can create a model like this:
DynamicDictionary dynamicDictionary = ToDynamicDictionary(dictionary);
return this
.Negotiate
.WithModel(dynamicDictionary)
.WithStatusCode(HttpStatusCode.OK);
Then, you have to convert your keys to a string, using this helper method:
private static DynamicDictionary ToDynamicDictionary(IDictionary<int, int> dictionary)
{
DynamicDictionary result = new DynamicDictionary();
foreach (var pair in dictionary)
{
result.Add(pair.Key.ToString(CultureInfo.InvariantCulture), pair.Value);
}
return result;
}
I'm trying to create some dynamic ExpandoObject. I've encountered a certain problem.
As I don't know what the name of these different properties in my objects should be, I can't do like this:
var list = new ArrayList();
var obj = new ExpandoObject();
obj.ID = 1,
obj.Product = "Pie",
obj.Days = 1,
obj.QTY = 65
list.Add(obj);
Let me explain my situation: I wish to get data from a random DB (I don't know which, but building a connection string from the information I get from the UI), therefore I don't know what data I need to get. This could be an example of a DB table
TABLE Sale
ID: int,
Product: nvarchar(100),
Days: int,
QTY: bigint
This could be another exmaple:
TABLE Foobar
Id: int,
Days: int
QTY: bigint
Product_Id: int
Department_Id: int
As you see, I don't know what the DB looks like (this is 100% anonymous, therefore it needs to be 100% dynamic), and the data I want to return should look like a well constructed JSON, like so:
[
{
"ID": 1,
"Product": "Pie"
"Days": 1,
"QTY": 65
},
{
"ID": 2,
"Product": "Melons"
"Days": 5,
"QTY": 12
}
]
Or, with the other example:
[
{
"ID": 1,
"Days": 2,
"QTY": 56,
"Product_Id": 5,
"Department_Id": 2
}
{
"ID": 2,
"Days": 6,
"QTY": 12,
"Product_Id": 2,
"Department_Id": 5
}
]
I've tried working with these ExpandoObjects, but can't seem to make it work, as I can't do what's illustrated in the top of this question (I don't know the names of the properties). Is there a way for me to say something like:
var obj = new ExpandoObject();
var propName = "Product";
var obj.propName = "Pie"
Console.WriteLine("Let's print!: " + obj.Product);
//OUTPUT
Let's print!: Pie
Does anyone have a solution, og simply guidance to a structure, that might solve this situation?
Rather than creating an ExpandoObject or some other dynamic type, you could create a List<Dictionary<string, object>> where each Dictionary<string, object> contains the name/value pairs you want to serialize. Then serialize to JSON using Json.NET (or JavaScriptSerializer, though that is less flexible):
var list = new List<Dictionary<string, object>>();
// Build a dictionary entry using a dictionary initializer: https://msdn.microsoft.com/en-us/library/bb531208.aspx
list.Add(new Dictionary<string, object> { { "ID", 1 }, {"Product", "Pie"}, {"Days", 1}, {"QTY", 65} });
// Build a dictionary entry incrementally
// See https://msdn.microsoft.com/en-us/library/xfhwa508%28v=vs.110%29.aspx
var dict = new Dictionary<string, object>();
dict["ID"] = 2;
dict["Product"] = "Melons";
dict["Days"] = 5;
dict["QTY"] = 12;
list.Add(dict);
Console.WriteLine(JsonConvert.SerializeObject(list, Formatting.Indented));
Console.WriteLine(new JavaScriptSerializer().Serialize(list));
The first outputs:
[
{
"ID": 1,
"Product": "Pie",
"Days": 1,
"QTY": 65
},
{
"ID": 2,
"Product": "Melons",
"Days": 5,
"QTY": 12
}
]
The second outputs the same without the indentation:
[{"ID":1,"Product":"Pie","Days":1,"QTY":65},{"ID":2,"Product":"Melons","Days":5,"QTY":12}]
Use dynamic, then cast to IDictionary<string, object> to loop through your properties:
dynamic obj = new ExpandoObject();
obj.Product = "Pie";
obj.Quantity = 2;
// Loop through all added properties
foreach(var prop in (IDictionary<string, object>)obj)
{
Console.WriteLine(prop.Key + " : " + prop.Value);
}
I've made a fiddle: https://dotnetfiddle.net/yFLy2u
Now this is a solution to your question... other answers like #dbc's might be better suited to the problem (which is not the question, really)
As you can see here ExpandoObject Class, the ExpandoObject is implementing IDictionary<string, object>, so you can use that fact like
IDictionary<string, object> obj = new ExpandoObject();
var propName = "Product";
obj[propName] = "Pie"
Console.WriteLine("Let's print!: " + obj[propName]);
// Verify it's working
Console.WriteLine("Let's print again!: " + ((dynamic)obj).Product);
While I was writing the answer, I see you already got proper answer. You can use a Dictionary<string, onject> or even Tuple.
But as per your original question, you wanted to add properties dynamically. For that you can refer to other answer using ExpandoObject. This is just the same solution (using ExpandoObject to dynamically add properties) with classes similar to your code.
//example classes
public class DictKey
{
public string DisplayName { get; set; }
public DictKey(string name) { DisplayName = name; }
}
public class DictValue
{
public int ColumnIndex { get; set; }
public DictValue(int idx) { ColumnIndex = idx; }
}
//utility method
public static IDictionary<string, object> GetExpando(KeyValuePair<DictKey, List<DictValue>> dictPair)
{
IDictionary<string, object> dynamicObject = new ExpandoObject();
dynamicObject["Station"] = dictPair.Key.DisplayName;
foreach (var item in dictPair.Value)
{
dynamicObject["Month" + (item.ColumnIndex + 1)] = item;
}
return dynamicObject;
}
Ans usage example:
var dictionaryByMonth = new Dictionary<DictKey, List<DictValue>>();
dictionaryByMonth.Add(new DictKey("Set1"), new List<DictValue> { new DictValue(0), new DictValue(2), new DictValue(4), new DictValue(6), new DictValue(8) });
dictionaryByMonth.Add(new DictKey("Set2"), new List<DictValue> { new DictValue(1), new DictValue(2), new DictValue(5), new DictValue(6), new DictValue(11) });
var rowsByMonth = dictionaryByMonth.Select(item => GetExpando(item));
First part, read this blog post by C# team thoroughly.
Lets see your code
var obj = new ExpandoObject();
var propName = "Product";
var obj.propName = "Pie"
Console.WriteLine("Let's print!: " + obj.Product);
//OUTPUT
Let's print!: Pie
In your code you are using var obj = new ExpandoObject();, so you are creating a statically typed object of type ExpandableObject. In the blog they specifically call out
I didn’t write ExpandoObject contact = new ExpandoObject(), because if I did contact would be a statically-typed object of the ExpandoObject type. And of course, statically-typed variables cannot add members at run time. So I used the new dynamic keyword instead of a type declaration, and since ExpandoObject supports dynamic operations, the code works
So if you rewrite your code to use dynamic obj, and add the dynamic properties as properties it should work!
But for your particular use case you better use Dictionaries as suggested above by #dbc
dynamic obj = new ExpandoObject();
obj.Product= "Pie"
Console.WriteLine("Let's print!: " + obj.Product);
//OUTPUT
Let's print!: Pie
I have JSON in the following format:
[
{"id":"10","name":"User","add":false,"edit":true,"authorize":true,"view":true},
{"id":"11","name":"Group","add":true,"edit":false,"authorize":false,"view":true},
{"id":"12","name":"Permission","add":true,"edit":true,"authorize":true,"view":true}
]
How can I convert that into a C# DataTable object as follows?
---------------------------------------------------------------------
ID | Name | Add | Edit | View | Authorize
---------------------------------------------------------------------
10 | User | true | true | true | true
11 | Group | true | true | true | true
12 | Permission| true | true | true | true
There is an easier method than the other answers here, which require first deserializing into a c# class, and then turning it into a datatable.
It is possible to go directly to a datatable, with JSON.NET and code like this:
DataTable dt = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));
Deserialize your jsonstring to some class
List<User> UserList = JsonConvert.DeserializeObject<List<User>>(jsonString);
Write following extension method to your project
using System.ComponentModel;
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for(int i = 0 ; i < props.Count ; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}
Call extension method like
UserList.ToDataTable<User>();
One doesn't always know the type into which to deserialize. So it would be handy to be able to take any JSON (that contains some array) and dynamically produce a table from that.
An issue can arise however, where the deserializer doesn't know where to look for the array to tabulate. When this happens, we get an error message similar to the following:
Unexpected JSON token when reading DataTable. Expected StartArray, got StartObject. Path '', line 1, position 1.
Even if we give it come encouragement or prepare our json accordingly, then "object" types within the array can still prevent tabulation from occurring, where the deserializer doesn't know how to represent the objects in terms of rows, etc. In this case, errors similar to the following occur:
Unexpected JSON token when reading DataTable: StartObject. Path '[0].__metadata', line 3, position 19.
The below example JSON includes both of these problematic features:
{
"results":
[
{
"Enabled": true,
"Id": 106,
"Name": "item 1",
},
{
"Enabled": false,
"Id": 107,
"Name": "item 2",
"__metadata": { "Id": 4013 }
}
]
}
So how can we resolve this, and still maintain the flexibility of not knowing the type into which to derialize?
Well here is a simple approach I came up with (assuming you are happy to ignore the object-type properties, such as __metadata in the above example):
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Data;
using System.Linq;
...
public static DataTable Tabulate(string json)
{
var jsonLinq = JObject.Parse(json);
// Find the first array using Linq
var srcArray = jsonLinq.Descendants().Where(d => d is JArray).First();
var trgArray = new JArray();
foreach (JObject row in srcArray.Children<JObject>())
{
var cleanRow = new JObject();
foreach (JProperty column in row.Properties())
{
// Only include JValue types
if (column.Value is JValue)
{
cleanRow.Add(column.Name, column.Value);
}
}
trgArray.Add(cleanRow);
}
return JsonConvert.DeserializeObject<DataTable>(trgArray.ToString());
}
I know this could be more "LINQy" and has absolutely zero exception handling, but hopefully the concept is conveyed.
We're starting to use more and more services at my work that spit back JSON, so freeing ourselves of strongly-typing everything, is my obvious preference because I'm lazy!
It can also be achieved using below code.
DataSet data = JsonConvert.DeserializeObject<DataSet>(json);
You can make use of JSON.Net here. Take a look at JsonConvert.DeserializeObject method.
I recommend you to use JSON.NET. it is an open source library to serialize and deserialize your c# objects into json and Json objects into .net objects ...
Serialization Example:
Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };
string json = JsonConvert.SerializeObject(product);
//{
// "Name": "Apple",
// "Expiry": new Date(1230422400000),
// "Price": 3.99,
// "Sizes": [
// "Small",
// "Medium",
// "Large"
// ]
//}
Product deserializedProduct = JsonConvert.DeserializeObject<Product>(json);
json = File.ReadAllText(System.AppDomain.CurrentDomain.BaseDirectory + "App_Data\\" +download_file[0]);
DataTable dt = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));
Here is another seamless approach to convert JSON to Datatable using Cinchoo ETL - an open source library
Sample below shows how to convert
string json = #"[
{""id"":""10"",""name"":""User"",""add"":false,""edit"":true,""authorize"":true,""view"":true},
{ ""id"":""11"",""name"":""Group"",""add"":true,""edit"":false,""authorize"":false,""view"":true},
{ ""id"":""12"",""name"":""Permission"",""add"":true,""edit"":true,""authorize"":true,""view"":true}
]";
using (var r = ChoJSONReader.LoadText(json))
{
var dt = r.AsDataTable();
}
Sample fiddle: https://dotnetfiddle.net/y0siCi
I solved it by the following - Used Pravin Pawar's answer below - for converting the JSON object to dt.
https://stackoverflow.com/a/11982180/504351
1)Send the JSON from Angular to C# controller method.
2)In C# Controller method -
convert the string data (JSON object) to a datatable.
DataTable dt = (DataTable)JsonConvert.DeserializeObject(data, (typeof(DataTable)));
3)Create a table in SQL Server database by parsing through rows and columns of the datatable dt
int dt_length = dt.Columns.Count;
Random rand = new Random();
string tablename = "Test" + rand.Next().ToString();
string query = "Create table " + tablename + " ( ";
for (int i = 0; i < dt_length; i ++)
{
if(i == dt_length -1)
{
query = query + dt.Columns[i].ColumnName + " varchar(max) ) ";
}
else
{
query = query + dt.Columns[i].ColumnName + " varchar(max), ";
}
}
4)Similarly insert each of the row data in the table created.
5)This helps to dynamically create a database table from JSON.