Flattening JSON-file c# - reduce number of foreach-loops - c#

I'm trying to deserialize a nested json-file that looks like the following sample:
The output i want is "flat" table. I'm using .net 4.0 and I do not have the option of using third-party librabies like json.net.
{
"responsetime": 33,
"products": {
"totalProducts": 25,
"products": [
{
"id": 1,
"name": "Bike One",
"colors": [
{
"colorId": 44,
"name": "green",
"chemicals": [
{
"chemicalId": 99,
"chemicalName": "abc"
},
{
"chemicalId": 45,
"chemicalName": "bcd"
}
]
},
{
"colorId": 42,
"name": "blue",
"chemicals": [
{
"chemicalId": 96,
"chemicalName": "def"
},
{
"chemicalId": 22,
"chemicalName": "lkj"
}
]
}
]
}
]
}
}
From this I have genereated the following classes:
public class ResponseObject
{
public int responsetime { get; set; }
public Products products { get; set; }
}
public class Products
{
public int totalProducts { get; set; }
public Product[] products { get; set; }
}
public class Product
{
public int id { get; set; }
public string name { get; set; }
public Color[] colors { get; set; }
}
public class Color
{
public int colorId { get; set; }
public string name { get; set; }
public Chemical[] chemicals { get; set; }
}
public class Chemical
{
public int chemicalId { get; set; }
public string chemicalName { get; set; }
}
The output i want is a flat structure like the following:
1 Bike One 44 green 99 abc
1 Bike One 44 green 45 bcd
1 Bike One 42 blue 96 def
1 Bike One 42 blue 22 lkj
I am able to obtain this with the following code, however this implies 3 foreach-loops which i'm afraid will give bad performance if there are i.e. 100.000 products with N colors and N chemicals each.
Is there any other way to flatten this that will perform better using "vanilla" .net?
String jsonFileContent = File.ReadAllText(#"C:\example.json");
JavaScriptSerializer js = new JavaScriptSerializer();
js.MaxJsonLength = Int32.MaxValue;
ResponseObject rspns = js.Deserialize<ResponseObject>(jsonFileContent);
foreach (var product in rspns.products.products)
{
foreach (var color in product.colors)
{
foreach (var chemical in color.chemicals)
{
Console.WriteLine(product.id);
Console.WriteLine(product.name);
Console.WriteLine(color.colorId);
Console.WriteLine(color.name);
Console.WriteLine(chemical.chemicalId);
Console.WriteLine(chemical.chemicalName);
}
}
}

You want to start having the ToString done by the same objects:
public class Products
{
public int totalProducts { get; set; }
public Product[] products { get; set; }
public override string ToString(){
// this iterates all products and stacks their string representation
var productsStrings = products.Select(x => x.ToString());
return productsStrings.Aggregate("", (a, n) => a + n + "\n").trimEnd();
}
}
public class Product
{
public int id { get; set; }
public string name { get; set; }
public Color[] colors { get; set; }
public override string ToString(){
// this gets all color strings and prepends the product string
// "1 Bike" + product (repeated CO times)
var colorSubstrings = colors.Select(x => x.GetSubstrings());
var appendOnStrings = colorSubstrings.Select(x => $"{id} {name} {x}");
return appendOnStrings.Aggregate("", (a, n) => a + n + "\n").trimEnd();
}
}
public class Color
{
public int colorId { get; set; }
public string name { get; set; }
public Chemical[] chemicals { get; set; }
public string[] GetSubstrings(){
// this gets all chemicals strings and prepends the color string
// "44 green" + chemicalString (repeated CH times)
return chemicals.Aggregate("", (a, chemical) => a + $"{colorId} {name} {chemical.ToString()} \n").trimEnd();
}
}
public class Chemical
{
public int chemicalId { get; set; }
public string chemicalName { get; set; }
public override string ToString(){
// this produces a string like -> "99 abc"
return $"{chemicalId} {chemicalName}";
}
}
Then you just parse the whole chain and call it:
String jsonFileContent = File.ReadAllText(#"C:\example.json");
JavaScriptSerializer js = new JavaScriptSerializer();
js.MaxJsonLength = Int32.MaxValue;
// deserializing JSON to rspns
ResponseObject rspns = js.Deserialize<ResponseObject>(jsonFileContent);
// result will contain the table with all products, colors and chemicals
var result = rspns.products.ToString();
// printing only once will surely improve the performances
Console.WriteLine(result);

Related

Deserializing JSON object array to List<MyOwnClass>

I'm trying to read this JSON array:
[{"name":"lc_cash","slot":1,"info":"","type":"item","amount":591},{"name":"advancedlockpick","slot":2,"info":[],"type":"item","amount":19}]
This is my code:
File Inventory.cs
class Item {
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("slot")]
public int Slot { get; set; }
[JsonProperty("info")]
public object Info { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
}
class Inventory
{
public List<Item> Item { get; set; }
}
File Form1.cs
Inventory inventory = new Inventory();
inventory = JsonSerializer.Deserialize<Inventory>(players.Inventory);
I'm getting this error:
System.Text.Json.JsonException: 'The JSON value could not be converted to Inventory. Path: $ | LineNumber: 0 | BytePositionInLine: 1.'
How can I read this correctly?
EDIT: testing with stackoverflow answers:
Main code:
List<Item> items = new List<Item>();
items = JsonSerializer.Deserialize<List<Item>>(players.Inventory);
Item class:
class Item {
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("slot")]
public int Slot { get; set; }
[JsonProperty("info")]
public string Info { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
}
Result: It's not throwing exception anymore but reading only 0 and nill
you will have to fix your json, one of the property info has value string, another has array. you have to select something one, null for example
var jsonParsed = JArray.Parse(json);
foreach (var item in jsonParsed)
{
if (((JObject)item)["info"].GetType().Name == "JArray"
|| ((JObject)item)["info"].GetType().Name == "JValue")
((JObject)item)["info"] = null;
}
List<Item> items = jsonParsed.ToObject<List<Item>>();
result
[
{
"name": "lc_cash",
"slot": 1,
"info": null,
"type": "item",
"amount": 591
},
{
"name": "advancedlockpick",
"slot": 2,
"info": null,
"type": "item",
"amount": 19
}
]
UPDATE
if you sometimes have
"info":{"food":30,"thirst":30}
it is better to change info property to this
[JsonProperty("info")]
public Dictionary<string,int> Info { get; set; }
or a little more complicated
[JsonProperty("info")]
public Info Info { get; set; }
public class Info
{
[JsonProperty("food")]
public int Food {get; set;}
[JsonProperty("thirst")]
public int Thirst {get; set;}
}
as long as the information here is right, you gave us a json array string.
the inventory is an object with an array, so the simple solution will be this:
Inventory inventory = new Inventory();
inventory.Item = JsonSerializer.Deserialize<List<Item>>(players.Inventory);
important to mention that since Item is a list, you should use plural name

Filter JSON Array based on element value in C#

I have this JSON by using: var root = JToken.Parse(myInputJson);
{{
"results": [
{
"orderId": "A123456789",
"genId": 3,
"invoiceId": "001",
"locId": "D07",
"group": "",
"assignedUserId": "TEST",
"billTo": 66537,
"shipTo": 66537,
"shipToName": "CONSTRUCTION TEST",
"pickCount": "1",
"shipBy": "UPS",
"isFromMultipleZones": false,
"taskState": "Assigned",
"boxes": []
},
{
"orderId": "B987654321",
"genId": 3,
"invoiceId": "001",
"locId": "D08",
"group": "",
"assignedUserId": "",
"billTo": 66537,
"shipTo": 66537,
"shipToName": "CONSTRUCTION TEST",
"pickCount": "4",
"shipBy": "UPS",
"isFromMultipleZones": false,
"taskState": "Assigned",
"boxes": []
}
]
}
What I need to is to get all the elements between the braces where the orderId = "B987654321".
After researching I have got this far but is not producing what I need:
var root = JToken.Parse(myInputJson);
var values = root.Where(t => (string)t["orderId"] == "B987654321").ToList();
I think I am close, can anyone see where my mistake is?
You forgot to index root by "results":
var root = JToken.Parse(text);
var values = root["results"].Where(t =>(string)t["orderId"] == "B987654321");
Also, your example JSON is badly formatted. If you delete the very first opening brace ({) it works
To get an order as JToken you can use this code
var results = JObject.Parse(json).SelectToken("results").ToArray();
var jsonOrder = results.First(o => o.SelectToken("orderId").ToString() == "B987654321");
but since you are using c# , it is much easier and safer to use an instance of Order class instead of JToken
Order order=jsonOrder.ToObject<Order>();
Order class
public partial class Order
{
[JsonProperty("orderId")]
public string OrderId { get; set; }
[JsonProperty("genId")]
public long GenId { get; set; }
[JsonProperty("invoiceId")]
public string InvoiceId { get; set; }
[JsonProperty("locId")]
public string LocId { get; set; }
[JsonProperty("group")]
public string Group { get; set; }
[JsonProperty("assignedUserId")]
public string AssignedUserId { get; set; }
[JsonProperty("billTo")]
public long BillTo { get; set; }
[JsonProperty("shipTo")]
public long ShipTo { get; set; }
[JsonProperty("shipToName")]
public string ShipToName { get; set; }
[JsonProperty("pickCount")]
[JsonConverter(typeof(ParseStringConverter))]
public long PickCount { get; set; }
[JsonProperty("shipBy")]
public string ShipBy { get; set; }
[JsonProperty("isFromMultipleZones")]
public bool IsFromMultipleZones { get; set; }
[JsonProperty("taskState")]
public string TaskState { get; set; }
[JsonProperty("boxes")]
public List<object> Boxes { get; set; }
}
But it is much easier to deseialize the whole json as net object instead of parsing. You can use linq to get any data.
List<Order> orders =JsonConvert.DeserializeObject<Root>(json).Orders;
Order order = orders.First(o => o.OrderId == "B987654321");
Root class
public partial class Root
{
[JsonProperty("results")]
public List<Order> Orders { get; set; }
}

How to Validate Duplicate Key values in JSON request

{
"number": 1165,
"lineItems": [{
"itemId": 1,
"dynamicfields": {
"styleId": "V-Neck",
"style": "T-SHIRTS",
"cost": 30
}
},
{
"itemId": 2,
"dynamicfields": {
"styleId": "V-Neck",
"style": "T-SHIRTS",
"cost": 30
}
}]
}
How can I validate dynamic fields for two different item id is same? I need to show validation message saying duplicate dynamic fields should not allow.
I have done some RND but there I am finding for JSON Keys duplication but I need Key Values duplication check.
Here is a demo to check json data.
Models:
public class ModelB{
public int number { get; set; }
public List<LineItem> lineItems { get; set; }
}
public class LineItem
{
public int itemId { get; set; }
public Dynamicfields dynamicfields { get; set; }
}
public class Dynamicfields {
public string styleId { get; set; }
public string style { get; set; }
public int cost { get; set; }
}
Action:
public string CheckRepeat([FromBody]ModelB modelB)
{
for (int i = 0; i < modelB.lineItems.Count() - 1; i++)
{
if (modelB.lineItems.Where(l => JsonConvert.SerializeObject(l.dynamicfields) == JsonConvert.SerializeObject(modelB.lineItems[i].dynamicfields)).Count() > 1)
{
return "duplicate dynamic fields should not allow";
}
}
return "";
}
result:
If you get jsondata,you can convert it to ModelB with:
ModelB modelB=JsonConvert.DeserializeObject<ModelB>(jsondata);

Deserialize Json into C#

I got some Json, that looks like this:
[
{
"starttime": "2020-02-27T14:30:00Z",
"endtime": "2020-02-27T14:40:00Z"
},
{
"Temp": {
"value": 3
},
"Pressure": {
"value": 29
},
"Humidity": {
"value": 85
}
}
]
I would like to deserialize it onto a object on the form:
public class Sample {
public string Name {get; set;}
public int Value {get;set;}
}
and then get 3 instances where name is set to either Temp, Pressure, Humidity, and Value set to 3, 29, 85
I don't really care about the start-/endtime part.
Any help would be greatly appreciated...
/Søren
Update:
Came up with this myself:
var tmp = JsonConvert.DeserializeObject<JArray>(content);
var samples = tmp.
SelectMany(x => ((JToken) x).Children())
.Where(x => !((JProperty) x).Name.Contains("time"))
.Select(x =>
{
var tmp2 = x.First.ToObject<Sample>();
tmp2.name = ((JProperty) x).Name;
return tmp2;
})
.ToList();
but I think Pavel's solution below, is more readable....
You can use Json.Linq to get a list of Sample objects from your json. Parse json to JArray instance, then enumerate all properties of the last object to get the names and values
var array = JArray.Parse(json);
var samples = ReadSamples(array.Last());
foreach (var sample in samples)
{
Console.WriteLine($"{sample.Name} {sample.Value}");
}
IEnumerable<Sample> ReadSamples(JToken data)
{
foreach (JProperty item in data)
{
yield return new Sample()
{
Name = item.Name,
Value = item.Value["value"]?.Value<int>() ?? 0
};
}
}
The output will be the following
Temp 3
Pressure 29
Humidity 85
It's also possible to do the same using System.Text.Json API, which is available from .NET Core 3.x
Per your posted JSON, you would get a model like below. Use http://json2csharp.com/#
public class Temp
{
public int value { get; set; }
}
public class Pressure
{
public int value { get; set; }
}
public class Humidity
{
public int value { get; set; }
}
public class RootObject
{
public DateTime starttime { get; set; }
public DateTime endtime { get; set; }
public Temp Temp { get; set; }
public Pressure Pressure { get; set; }
public Humidity Humidity { get; set; }
}
If your JSON is not dynamic, creating classes which model your JSON is a good idea.
An easy way is to Copy JSON to clipboard -> Open Visual Studio -> Edit -> Paste Special -> Paste JSON as classes.
This should give you the following classes:
public class Class1
{
public DateTime starttime { get; set; }
public DateTime endtime { get; set; }
public Temp Temp { get; set; }
public Pressure Pressure { get; set; }
public Humidity Humidity { get; set; }
}
public class Temp
{
public int value { get; set; }
}
public class Pressure
{
public int value { get; set; }
}
public class Humidity
{
public int value { get; set; }
}
And now you can deserialize the JSON array to List<Class1>using the Newtonsoft.Json NuGet package:
using Newtonsoft.Json;
using System.Collections.Generic;
...
string json = #"[
{
""starttime"": ""2020 - 02 - 27T14: 30:00Z"",
""endtime"": ""2020-02-27T14:40:00Z""
},
{
""Temp"": {
""value"": 3
},
""Pressure"": {
""value"": 29
},
""Humidity"": {
""value"": 85
}
}
]";
var myObject = JsonConvert.DeserializeObject<List<Class1>>(json);

Deserialise JSON iin C# using system.text.json with complex property name

I am trying to deserialise JSON files that have been created by a third party tool.
The files contain a property that can look like the below:
"RECIPE": [{
"ctPoint_0": {
"endTemperature": 25,
"hours": 0.07999999821186066,
"startTemperature": 25
},
"ctPoint_1": {
"endTemperature": 30,
"hours": 0.07999999821186066,
"startTemperature": 25
},
"ctPoint_2": {
"endTemperature": 25,
"hours": 0.07999999821186066,
"startTemperature": 30
},
"pointCount": 3,
"type": "CyclicTemp"
}, {
"cycles": 2,
"type": "Repeat"
}, {
"cycles": 1,
"duration": {
"days": 0,
"hours": 0
},
}]
I am using system.text.json to deserialise:
newProgram = JsonSerializer.Deserialize<Program>(jsonString, options);
Extract of my Program class:
public class Program
{
public IList<Recipe> RECIPE { get; set; }
}
Recipe and cyclic_data struct:
public struct Cyclic_Data
{
public float endTemperature { get; set; }
public float hours { get; set; }
public float startTemperature { get; set; }
}
public struct Recipe
{
public int cycles { get; set; }
// Would like to use this
public IList<Cyclic_Data> ctPoints { get; set; }
// this deserialises ok but obviously only to hardcoded limit
public Cyclic_Data ctPoint_0 { get; set; }
public Cyclic_Data ctPoint_1 { get; set; }
public Cyclic_Data ctPoint_2 { get; set; }
public Cyclic_Data ctPoint_3 { get; set; }
public Cyclic_Data ctPoint_4 { get; set; }
public Cyclic_Data ctPoint_5 { get; set; }
public Cyclic_Data ctPoint_6 { get; set; }
public Cyclic_Data ctPoint_7 { get; set; }
public Cyclic_Data ctPoint_8 { get; set; }
public Cyclic_Data ctPoint_9 { get; set; }
public Cyclic_Data ctPoint_10 { get; set; }
public Duration duration { get; set;}
public string type { get; set; }
public float temperature { get; set; }
public int pointCount { get; set; }
}
As per the comments, if I have a number of discrete variables of type Cyclic_Data e.g. ctPoint_0 then this successfully deserialises, however as this list could theoretically be arbitrarily large it would be a nonsense to try to declare all possible property names.
I would really like to use an IList to read in all ctPoint_X values but am struggling to find a way to do so. I was looking at the newton soft implementation instead and wondering whether [JsonProperty("name")] could be used with RegEx to solve this but could not find any successful example done in this way.
How can I deserialise this in a sensible manner?
EDIT:
I am currently looking at a custom JsonNamingPolicy to rename any property name matching a RegEx "^ctPoint_[0-9]+$" to ctPoints, will let you know if this succeeds, please comment if this is doomed to fail or if there is a better way..
EDIT 2:
I tried the method outlined above but it didn't work as the correct JSON for a list of items doesn't have the name at beginning of each item, however this started me thinking about the problem differently. What I ended up doing was some simple string replacements before the deserialisation. This worked fine :)
int location;
location = newText.IndexOf("\"ctPoint_0\":");
newText = newText.Replace("\"ctPoint_0\":", "\"ctPoints\": [");
if (location > 0)
{ int lastloc;
for (int i = 1; i < 999999; i++)
{
string nextString = "\"ctPoint_" + i.ToString() + "\": ";
lastloc = location;
location = newText.IndexOf(nextString);
newText = newText.Replace(nextString, "");
if (location == -1)
{
location = newText.IndexOf("}", lastloc);
newText = newText.Insert(location+1, "]");
break;
}
}
}
Thanks
You can try Dictionary<string, object> and check the object type during runtime
Here are the models required for this scenario
public class CtPoint
{
public int endTemperature { get; set; }
public double hours { get; set; }
public int startTemperature { get; set; }
}
public class Duration
{
public int days { get; set; }
public int hours { get; set; }
}
Here is the sample code to Deserialize the JSON using System.Text.Json.JsonSerializer
var results = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string,object>>>(json);
foreach (var model in results)
{
foreach(var item in model)
{
if (item.Key.Contains("ctPoint"))
{
var ctPoint = System.Text.Json.JsonSerializer.Deserialize<CtPoint>(item.Value.ToString());
Console.WriteLine($"{item.Key}- {ctPoint.hours} {ctPoint.startTemperature} {ctPoint.endTemperature}");
}
else if (item.Key.Contains("duration"))
{
var duration = System.Text.Json.JsonSerializer.Deserialize<Duration>(item.Value.ToString());
Console.WriteLine($"{item.Key}- {duration.days} {duration.hours}");
}
else
{
Console.WriteLine($"{item.Key}- {item.Value.ToString()}");
}
}
}
Output
ctPoint_0- 0,0799999982118607 25 25
ctPoint_1- 0,0799999982118607 25 30
ctPoint_2- 0,0799999982118607 30 25
pointCount- 3
type- CyclicTemp
cycles- 2
type- Repeat
cycles- 1
duration- 0 0

Categories