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.
Related
I have a question regarding logic in C# of the following. I need to generate a file where the order of the fields are specified by the input JSON. For example, I have three fields in my output:
{ID, Name, Value}
The order of these fields are specified in the JSON file i.e.
ID = 1
Name = 2
Value = 3
So, if I need to change an order of the field I just do it in my JSON file which is added to the project.
try this
using Newtonsoft.Json;
var json = "{\"FileConf\": [ { \"ID\" : 1, \"Name\" : 2, \"Value\" : 3 } ]}";
var jArray = (JArray)JObject.Parse(json)["FileConf"];
var arr = jArray.Select(a => new string[] { a["ID"].ToString(),
a["Name"].ToString(), a["Value"].ToString() });
var sb= new StringBuilder();
foreach (var item in arr)
{
sb.Append(string.Join(",",item));
sb.Append(Environment.NewLine);
}
File.WriteAllText("somefile.csv",sb.ToString());
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"
Does anyone know how to convert the below nested JSON to CSV via CHOETL (An ETL framework for .NET)? Thank you!
I'm using this code but it will only return the first equipment record.
CODE:
{
using (var json = new ChoJSONReader("./test.json"))
{
csv.Write(json.Cast<dynamic>().Select(i => new
{
EquipmentId = i.GpsLocation.Equipment[0].EquipmentId,
InquiryValue = i.GpsLocation.Equipment[0].InquiryValue,
Timestamp = i.GpsLocation.Equipment[0].Timestamp
}));
}
}
JSON:
"GpsLocation": {
"Equipment": [
{
"EquipmentId": "EQ00001",
"InquiryValue": [
"IV00001"
],
"Timestamp": "2020-01-01 01:01:01.01",
},
{
"EquipmentId": "EQ00002",
"InquiryValue": [
"IV00002"
],
"Timestamp": "2020-01-01 01:01:01.01"
}
]
}
}````
As others suggest, the issue is you are only looking at the first element of the array.
It appears that the easiest way to control what you serialise into CSV is by correctly defining your source objects from JSON. JSON Path expressions come in pretty handy.
What I ended up doing here is query all JSON to return an array of Equipment objects regardless of where they are in the hierarchy (which means you may need to filter it a bit better depending on your full JSON).
Then it's pretty easy to define each field based on JSON path and just pass the result to CSVWriter.
Also check out some gotchas that I outlined in the respective comment lines.
void Main()
{
var jsonString = "{\"GpsLocation\":{\"Equipment\":[{\"EquipmentId\":\"EQ00001\",\"InquiryValue\":[\"IV00001\"],\"Timestamp\":\"2020-01-01 01:01:01.01\"},{\"EquipmentId\":\"EQ00002\",\"InquiryValue\":[\"IV00002\"],\"Timestamp\":\"2020-01-01 01:01:01.01\"}]}}";
var jsonReader = new StringReader(jsonString);
var csvWriter = new StringWriter(); // outputs to string, comment out if you want file output
//var csvWriter = new StreamWriter(".\\your_output.csv"); // writes to a file of your choice
using (var csv = new ChoCSVWriter(csvWriter))
using (var json = new ChoJSONReader(jsonReader)
.WithJSONPath("$..Equipment[*]", true) // firstly you scope the reader to all Equipment objects. take note of the second parameter. Apparently you need to pass true here as otherwise it just won't return anythig
.WithField("EquipmentId", jsonPath: "$.EquipmentId", isArray: false) // then you scope each field in the array to what you want it to be. Since you want scalar values, pass `isArray: false` for better predictability
.WithField("InquiryValue", jsonPath: "$.InquiryValue[0]", isArray: false) // since your InquiryValue is actually an array, you want to obtain first element here. if you don't do this, fields names and values would go askew
.WithField("Timestamp", jsonPath: "$.Timestamp", fieldType: typeof(DateTime), isArray: false)) // you can also supply field type, otherwise it seems to default to `string`
{
csv.WithFirstLineHeader().Write(json);
}
Console.WriteLine(csvWriter.GetStringBuilder().ToString()); // comment this out if writing to file - you won't need it
}
Update summary:
Pivoted to update the code to rely on JSON Path scoping - this seems to allow for field name manipulation with pretty low effort
Looking at your comment, you could probably simplify your file writer a little bit - use StreamWriter instead of StringWriter - see updated code for example
Here is the working sample of producing CSV from your JSON
string json = #"{
""GpsLocation"": {
""Equipment"": [
{
""EquipmentId"": ""EQ00001"",
""InquiryValue"": [
""IV00001""
],
""Timestamp"": ""2020-02-01 01:01:01.01"",
},
{
""EquipmentId"": ""EQ00002"",
""InquiryValue"": [
""IV00002""
],
""Timestamp"": ""2020-01-01 01:01:01.01""
}
]
}
}";
StringBuilder csv = new StringBuilder();
using (var r = ChoJSONReader.LoadText(json)
.WithJSONPath("$.GpsLocation.Equipment")
.WithField("EquipmentId")
.WithField("InquiryValue", jsonPath: "InquiryValue[0]", fieldType: typeof(string))
.WithField("Timestamp", fieldType: typeof(DateTime))
)
{
using (var w = new ChoCSVWriter(csv)
.WithFirstLineHeader())
w.Write(r);
}
Console.WriteLine(csv.ToString());
Output:
EquipmentId,InquiryValue,Timestamp
EQ00001,IV00001,2/1/2020 1:01:01 AM
EQ00002,IV00002,1/1/2020 1:01:01 AM
Sample fiddle: https://dotnetfiddle.net/hJWtqH
Your code is sound, but the issue is that you're only writing the first variable in the array by using i.GpsLocation.Equipment[0]. Instead, try looping over everything by putting it into a for loop, and changing the [0] to your iterating variable inside of said loop.
I am using EntityFramework, C# and MVC to build a web app. I need to create a dynamic table in RAM like this
COL1 | COL2 | COL3... etc.
1 | Val | Val
2 | Val | Val
I then need to be able to read/write it to JSON in the following format:
[
{id:1, COL1: "Val", COL2: "Val", COL3: "Val" },
{id:2, COL1: "Val", COL2: "Val", COL3: "Val" }
];
I need to be able to read and write the table to this format, dynamically add columns and rows easily.
I have tried using DataTable but that causes this error when it serializes:
Error: A circular reference was detected while serializing an object of type 'System.Reflection.RuntimeModule'.
Any recommendations would be appreciated.
EDIT: This is the code I used to build the table that causes the error, even when using JSON.NET serialization:
DataTable datatable = new DataTable();
datatable.Columns.Add("DBID");
datatable.Columns.Add("ROW");
datatable.Columns.Add("ID");
DataRow row = datatable.NewRow();
row["DBID"] = "Test";
row["ROW"] = "Test";
row["ID"] = "Test";
datatable.Rows.Add(row);
string json = JsonConvert.SerializeObject(datatable, Formatting.Indented); // <- Error here
return Content(json, "application/json");
EDIT 2 - SOLUTION:
string json = JsonConvert.SerializeObject(datatable, Formatting.Indented,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
}
);
Thanks alan and T.S. for pointing me in the right direction and everyone else who answered!
As mentioned in many other posts, you can always add a reference to JSON.NET which exposes the method below:
var json = JsonConvert.SerializeObject(dataTableToConvertToJSON,
Formatting.Indented);
Update:
Try this code instead. It should take care of the error you're getting:
var dataTableToConvertToJSON = JsonConvert.SerializeObject(dataTableToConvertToJSON,
Formatting.Indented,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
To return the Json as a string use this:
return json;
To return a Json object use this:
return Json(dataTableToConvertToJSON, JsonRequestBehavior.AllowGet);
Like a variant your table can be stored like this:
public class TestClass
{
public int Id { get; set; }
public Dictionary<string,string> CR { get; set; } //Columns And Rows
}
And then create a list of this class, which represent your table : List<TestClass> and then serialize or deserialize it with custom converter.
P.S: alan idea is better)
Since you are importing/exporting this data to/from JSON, how about doing this on client side using JavaScript, though I am not sure whether that is at all an option for you or not. However, doing this gymnastic with JavaScript supports your requirement of adding col/properties dynamically, if that is what you need absolutely.
Otherwise having a simple C# class and constructing a list with that class' objects and then serializing the list should also do the job.
Below is an instance of a simple job scheduler which parses xml dynamic strings to json:
XML
<Navigations>
<Navigation Name="facebook" Active ="0" ></Navigation>
</Navigations>
c#
List<NavigationData> nds = new List<NavigationData>();
foreach (object cnav in (IEnumerable)c.Navigations)
{
NavigationData nd = new NavigationData();
nd.Name = (string)((dynamic)cnav).Name;
nd.Active = XmlConvert.ToBoolean((string)((dynamic)cnav).Active); // 3
nds.Add(nd);
}
transitContent.NavigationData = JsonConvert.SerializeObject(nds);
The above program throws an exception at line 3 as:
failed to convert string to boolean with XMLConvert.ToBoolean
not able to recognize string with Convert.ToBoolean
An other type conversions possibele in this scenario? The expected result should be:
JSON
[
{
"Name": "facebook",
"Active": false
}
]
Well yes, "0" isn't a valid value for a Boolean. It sounds like you possibly want something like:
List<NavigationData> nds = new List<NavigationData>();
foreach (dynamic cnav in (IEnumerable)c.Navigations)
{
NavigationData nd = new NavigationData();
nd.Name = cnav.Name;
nd.Active = cnav.Active != "0";
nds.Add(nd);
}
transitContent.NavigationData = JsonConvert.SerializeObject(nds);
This is assuming that cnav will expose all properties as strings (as their execution-time type).