Shorten JSON reading class - c#

I have a simple JSON reading class that should grab values from a JSON object and put it in c# variables. Right now it uses 8 if statements, but I was wondering if this can be done in a smoother way.
current code:
public Game Read(string filePath)
{
string fileName = "./Levels/TempleOfDoom.json";
JObject json = JObject.Parse(File.ReadAllText(fileName));
Game game = new Game();
foreach (JObject jconnection in json["rooms"])
{
Room room = new Room();
foreach (JProperty jProperty in jconnection.Children().OfType<JProperty>())
{
if (jProperty.Name == "id")
room.id = jProperty.Value.ToObject<int>();
if (jProperty.Name == "width")
room.width = jProperty.Value.ToObject<int>();
if (jProperty.Name == "height")
room.height = jProperty.Value.ToObject<int>();
foreach (JObject jconnection2 in jconnection["items"])
{
Item item = new Item();
foreach (JProperty jProperty2 in jconnection.Children().OfType<JProperty>())
{
if (jProperty.Name == "type")
item.type = jProperty2.Value.ToObject<string>();
if (jProperty.Name == "x")
item.x = jProperty2.Value.ToObject<int>();
if (jProperty.Name == "y")
item.y = jProperty2.Value.ToObject<int>();
if (jProperty.Name == "damage")
item.damage = jProperty2.Value.ToObject<int>();
if (jProperty.Name == "color")
item.color = jProperty2.Value.ToObject<string>();
}
}
}
game.Rooms.Add(room);
}
return game;
}
relevant part of JSON object:
{
"rooms": [
{
"id": 1,
"type": "room",
"width": 5,
"height": 5
},
{
"id": 2,
"type": "room",
"width": 3,
"height": 3
},
{
"id": 3,
"type": "room",
"width": 5,
"height": 5,
"items": [
{
"type": "disappearing boobietrap",
"damage": 1,
"x": 2,
"y": 1
},
{
"type": "sankara stone",
"x": 2,
"y": 2
}
]
},
{
"id": 4,
"type": "room",
"width": 11,
"height": 7,
"items": [
{
"type": "key",
"color": "green",
"x": 1,
"y": 1
},
{
"type": "sankara stone",
"x": 5,
"y": 3
},
{
"type": "boobietrap",
"damage": 1,
"x": 4,
"y": 2
},
{
"type": "boobietrap",
"damage": 1,
"x": 5,
"y": 2
},
{
"type": "boobietrap",
"damage": 1,
"x": 6,
"y": 2
},
{
"type": "boobietrap",
"damage": 1,
"x": 4,
"y": 4
},
{
"type": "boobietrap",
"damage": 1,
"x": 5,
"y": 4
},
{
"type": "boobietrap",
"damage": 1,
"x": 6,
"y": 4
}
]
},
{
"id": 5,
"type": "room",
"width": 5,
"height": 5,
"items": [
{
"type": "key",
"color": "red",
"x": 2,
"y": 3
},
{
"type": "sankara stone",
"x": 2,
"y": 2
}
]
},
{
"id": 6,
"type": "room",
"width": 3,
"height": 3,
"items": [
{
"type": "sankara stone",
"x": 1,
"y": 1
}
]
},
{
"id": 7,
"type": "room",
"width": 5,
"height": 3,
"items": [
{
"type": "pressure plate",
"x": 2,
"y": 1
}
]
},
{
"id": 8,
"type": "room",
"width": 3,
"height": 3
},
{
"id": 9,
"type": "room",
"width": 5,
"height": 5,
"items": [
{
"type": "sankara stone",
"x": 2,
"y": 2
},
{
"type": "boobietrap",
"damage": 1,
"x": 1,
"y": 3
},
{
"type": "boobietrap",
"damage": 1,
"x": 2,
"y": 3
},
{
"type": "boobietrap",
"damage": 1,
"x": 3,
"y": 3
}
]
}
],
}
As you can see, each room has an ID, width and height (type can be ignored) and some rooms have items, which all have a type, x and y coordinate, and some have colors or damage numbers. Is there a better way to get all of these values into a C# class? A game has a List of rooms, and each room can possibly have a list of items.
EDIT: Thanks guys! These lines of code did exactly what I wanted (together with some extra classes for each object game/player/item etc.
public Game Read(string fileName)
{
JObject json = JObject.Parse(File.ReadAllText(fileName));
return JsonConvert.DeserializeObject<Game>(json.ToString());
}

My advice would be to use a webpage called https://json2csharp.com/ that it will create the model class for your json. Sometimes it needs a little tweaking, then is as easy as calling
var json = JsonConverter.DeserializeObject<YourJsonClass>(File.ReadAllText(fileName))
And you would have a class representing your json file, and if you still need to move this information to your custom classes you wouldnt need the if at all just do something like
foreach (var jsonRoom in json.Rooms)
{
room.id = jsonRoom.id;
//and so on
}

You can create a class, for example:
public class Item
{
public string type { get; set; }
public int damage { get; set; }
public int x { get; set; }
public int y { get; set; }
public string color { get; set; }
}
public class Room
{
public int id { get; set; }
public string type { get; set; }
public int width { get; set; }
public int height { get; set; }
public List<Item> items { get; set; }
}
public class MyClass
{
public List<Room> rooms { get; set; }
}
And then deserialize the object as:
MyClass myDeserializedClass = JsonConvert.DeserializeObject<MyClass>(json.ToString());
And then you can access all inside of MyClass and iterate over List<Room> using foreach

Related

How I can use double grouped elements using LINQ?

In my project I would like to group double time first by project then group by daily.
For ex: Project X is using for 06.11.2020 (300$ price), for 07.11.2020 (250 $) etc.
I would like to achieve some result like this:
[
{
projectId: 1,
projectName: 'Test',
data: [
{
price: 22.000000176,
startDate: '2020-11-06T00:00:00'
},
{
price: 42.000000176,
startDate: '2020-11-07T00:00:00'
},
{
price: 62.000000176,
startDate: '2020-11-08T00:00:00'
},
],
},
{
projectId: 2,
projectName: 'Test 2',
data: [
{
price: 333.000000176,
startDate: '2020-11-06T00:00:00'
},
{
price: 3333.000000176,
startDate: '2020-11-07T00:00:00'
},
{
price: 3333.000000176,
startDate: '2020-11-08T00:00:00'
},
],
},
]
What I am trying in my code
public async Task<List<GroupedBillingInfo>> Handle(GroupedBillingListQuery request,
CancellationToken cancellationToken)
{
var billing = _context.BillingSummaries.Where(x => x.Project.Organization.IsDeleted == false)
.AsQueryable();
var demo = billing.ToList();
var result = demo.GroupBy(x => new {
ProjectId = x.ProjectId,
Year = x.BeginApply.Year,
Month = x.BeginApply.Month,
Day = x.BeginApply.Day
})
.Select(y => new GroupedBillingInfo
{
ProjectId = y.Key.ProjectId,
ProjectName = y.Select(d => d.Project.Name).FirstOrDefault(),
Data = new List<GroupedBillings>
{
new GroupedBillings
{
StartDate = $"{y.Key.Year}/{y.Key.Month}/{y.Key.Day}",
Price = y.Sum(s => s.Price)
}
}
}).ToList();
return result;
}
public class GroupedBillings
{
public string StartDate { get; set; }
public decimal Price { get; set; }
}
public class GroupedBillingInfo
{
public List<GroupedBillings> Data { get; set; }
public int ProjectId { get; set; }
public string ProjectName { get; set; }
}
And after this I am getting results:
[
{
"data": [
{
"startDate": "2020/11/6",
"price": 26.66666688
}
],
"projectId": 50,
"projectName": "test-bug"
},
{
"data": [
{
"startDate": "2020/11/6",
"price": 33.333333599999996
}
],
"projectId": 38,
"projectName": "tcpro-demo"
},
{
"data": [
{
"startDate": "2020/11/6",
"price": 40.00000032
}
],
"projectId": 53,
"projectName": "loki-test"
},
{
"data": [
{
"startDate": "2020/11/6",
"price": 12.000000096
}
],
"projectId": 64,
"projectName": "chaos"
},
{
"data": [
{
"startDate": "2020/11/7",
"price": 26.66666688
}
],
"projectId": 64,
"projectName": "chaos"
},
{
"data": [
{
"startDate": "2020/11/7",
"price": 32.000000256
}
],
"projectId": 50,
"projectName": "test-bug"
},
{
"data": [
{
"startDate": "2020/11/7",
"price": 40.00000032
}
],
"projectId": 38,
"projectName": "tcpro-demo"
},
{
"data": [
{
"startDate": "2020/11/7",
"price": 48.000000384
}
],
"projectId": 53,
"projectName": "loki-test"
},
{
"data": [
{
"startDate": "2020/11/8",
"price": 32.000000256
}
],
"projectId": 64,
"projectName": "chaos"
},
{
"data": [
{
"startDate": "2020/11/8",
"price": 32.000000256
}
],
"projectId": 50,
"projectName": "test-bug"
},
{
"data": [
{
"startDate": "2020/11/8",
"price": 40.00000032
}
],
"projectId": 38,
"projectName": "tcpro-demo"
},
{
"data": [
{
"startDate": "2020/11/8",
"price": 48.000000384
}
],
"projectId": 53,
"projectName": "loki-test"
},
{
"data": [
{
"startDate": "2020/11/9",
"price": 17.333333472
}
],
"projectId": 64,
"projectName": "chaos"
},
{
"data": [
{
"startDate": "2020/11/9",
"price": 18.666666816
}
],
"projectId": 50,
"projectName": "test-bug"
},
{
"data": [
{
"startDate": "2020/11/9",
"price": 23.333333519999996
}
],
"projectId": 38,
"projectName": "tcpro-demo"
},
{
"data": [
{
"startDate": "2020/11/9",
"price": 28.000000224
}
],
"projectId": 53,
"projectName": "loki-test"
}
]
So in short I would like (project and inside object -> daily datetimes and price sum,
Project x -> (06nov2020 -> price 300 || 07nov2020 -> price 100)
You should not group it at once. You want select a records with unique projectId, and group by date. It should be two different groupings instead
var result = demo
.GroupBy(x => new {
ProjectId = x.ProjectId,
Year = x.BeginApply.Year,
Month = x.BeginApply.Month,
Day = x.BeginApply.Day
})
.GroupBy(x => new {
ProjectId = x.Key.ProjectId
})
.Select(y => new GroupedBillingInfo
{
ProjectId = y.Key.ProjectId,
ProjectName = y.First().Select(d => d.Project.Name).First(),
Data = y.Select(z => new GroupedBillings
{
StartDate = $"{z.Key.Year}/{z.Key.Month}/{z.Key.Day}",
Price = z.Sum(s => s.Price)
}).ToList()
}).ToList();

Sort the sum aggregate - Nest (ElasticSearch)

The requirement is to group by Employee Id and Name and then aggregate the sum(salary). I have managed to get it to work. However, the final aggregated result has to be sorted in descending order of the sum. I tried couple of ways but the ordering doesn't seem to work.
var resp =
elasticClient.Search<Employee>(
k => k.Size(0).Query(q => q.QueryString(s => s.Query(query)))
.Aggregations(a => a.Terms("EmpId", v => v.Field(f => f.EmpId)
.Aggregations(aa => aa.Terms("EmpName",vv => vv.Field(ff => ff.EmpName).OrderDescending("Salary")
.Aggregations(aaa => aaa.Sum("Salary", sa => sa.Field(fff => fff.Salary))))))));
Employee Class:
public sealed class Employee
{
public long Id { get; set; }
public int DateId { get; set; }
public int? DivisionNumber { get; set; }
public string Manager { get; set; }
public decimal Salary { get; set; }
public int UpdateId { get; set; }
public DateTime UpdateTimestamp { get; set; }
public int EmpId { get; set; }
public string EmpName { get; set; }
}
Mapping:
POST /sample_employee
{
"mappings": {
"post":{
"properties": {
"empId": {
"type" : "long"
},
"empName": {
"type": "string",
"index": "not_analyzed"
},
"salary": {
"type": "float"
},
"dateId": {
"type": "long"
},
"divisionNumber":{
"type": "long"
}
}
}
}
}
Query for aggregation:
GET /sample_employee/_search
{
"size": 0,
"query": {
"query_string": {
"query": "dateId:(1780) AND divisionNumber:(1)"
}
},
"aggs": {
"EmpId": {
"terms": {
"field": "empId"
},
"aggs": {
"EmpName": {
"terms": {
"field": "empName",
"size": 30,
"order":{
"Amount.value": "desc"}
},
"aggs": {
"Amount": {
"sum": {
"field": "salary"
}
}
}
}
}
}
}
}
Response:
"aggregations": {
"EmpId": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 10,
"doc_count": 1,
"EmpName": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Jerry Mathews",
"doc_count": 1,
"Amount": {
"value": 10000
}
}
]
}
},
{
"key": 11,
"doc_count": 1,
"EmpName": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Tom Raju",
"doc_count": 1,
"Amount": {
"value": 15000
}
}
]
}
},
{
"key": 12,
"doc_count": 1,
"EmpName": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Nel Gad",
"doc_count": 1,
"Amount": {
"value": 20000
}
}
]
}
},
{
"key": 13,
"doc_count": 1,
"EmpName": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Jim Thomas",
"doc_count": 1,
"Amount": {
"value": 25000
}
}
]
}
},
{
"key": 14,
"doc_count": 1,
"EmpName": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Amat Ahu",
"doc_count": 1,
"Amount": {
"value": 30000
}
}
]
}
}
]
}
}
}

How to parse this json in List<Class>?

I am developing windows form application in framework 4.0. I am using JSON.NET. I have the following json in a string variable. I have the 10 classes in the json as follows. How to parse the following json and convert it into List
[
{
"Class": 1,
"ClassUrl": "EngineeringSemister12014A",
"OEPTitle": "Engineer",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
}
],
"IsArchived": true
},
{
"Class": 7,
"ClassUrl": "Engineer2014A",
"OEPTitle": "Engineer",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
}
]
},
{
"Class": 8,
"ClassUrl": "Engineer22014A",
"OEPTitle": "Engineer2",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
}
]
},
{
"Class": 9,
"ClassUrl": "Engineer32014A",
"OEPTitle": "Engineer3",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
}
]
},
{
"Class": 10,
"ClassUrl": "Engineer32014B",
"OEPTitle": "Engineer3",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
},
{
"Id": 8,
"OEPTitle": "english"
}
],
"IsRecycled": true
},
{
"Class": 12,
"ClassUrl": "Engineer52014B",
"OEPTitle": "Engineer5",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
},
{
"Id": 8,
"OEPTitle": "english"
}
]
},
{
"Class": 13,
"ClassUrl": "Engineer62014B",
"OEPTitle": "Engineer6",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
},
{
"Id": 8,
"OEPTitle": "english"
}
]
},
{
"Class": 16,
"ClassUrl": "Mechanical2014undefined",
"OEPTitle": "Mechanical Branch",
"Subject": [
{
"Id": 6,
"OEPTitle": "history"
},
{
"Id": 4,
"OEPTitle": "chemical "
}
]
},
{
"Class": 6,
"ClassUrl": "Engineer12014A",
"OEPTitle": "Engineer1",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
},
{
"Id": 6,
"OEPTitle": "history"
},
{
"Id": 4,
"OEPTitle": "chemical "
}
]
},
{
"Class": 11,
"ClassUrl": "Engineer42014B",
"OEPTitle": "Engineer4",
"Subject": [
{
"Id": 1,
"OEPTitle": "Algebra"
},
{
"Id": 2,
"OEPTitle": "Maths"
},
{
"Id": 6,
"OEPTitle": "history"
},
{
"Id": 4,
"OEPTitle": "chemical "
},
{
"Id": 8,
"OEPTitle": "english"
}
]
}
]
Using json2csharp:
public class Subject
{
public int Id { get; set; }
public string OEPTitle { get; set; }
}
public class RootObject
{
public int Class { get; set; }
public string ClassUrl { get; set; }
public string OEPTitle { get; set; }
public List<Subject> Subject { get; set; }
public bool IsArchived { get; set; }
}
and now, you can use JSON.NET:
List<RootObject> rootObj = JsonConvert.Deserialize<List<RootObject>>(yourJsonHere);

Not able to convert string to json

i am getting measurements from withings and want to show them in graphs but not able to convert it to json. i also try JsonConvert.SerializeObject(myString) using Newtonsoft.dlland simple
System.Web.Script.Serialization.JavaScriptSerializer sr = new System.Web.Script.Serialization.JavaScriptSerializer();
sr.Serialize(myString);
but it is not converting.
My withings measurement string is as follows.
{
"status": 0,
"body": {
"updatetime": 1392764547,
"measuregrps": [
{
"grpid": 17945868,
"attrib": 0,
"date": 139984270,
"category": 1,
"measures": [
{
"value": 72,
"type": 9,
"unit": 0
},
{
"value": 152,
"type": 10,
"unit": 7
},
{
"value": 87,
"type": 17,
"unit": 0
}
]
},
{
"grpid": 176587495,
"attrib": 0,
"date": 13915689,
"category": 1,
"measures": [
{
"value": 94,
"type": 9,
"unit": 0
},
{
"value": 145,
"type": 10,
"unit": 0
},
{
"value": 109,
"type": 11,
"unit": 0
}
]
},
{
"grpid": 179262494,
"attrib": 0,
"date": 1391369607,
"category": 1,
"measures": [
{
"value": 77,
"type": 9,
"unit": 0
},
{
"value": 121,
"type": 10,
"unit": 0
},
{
"value": 87,
"type": 11,
"unit": 0
}
]
},
{
"grpid": 179258492,
"attrib": 0,
"date": 1391171167,
"category": 1,
"measures": [
{
"value": 61,
"type": 9,
"unit": 0
},
{
"value": 107,
"type": 10,
"unit": 0
},
{
"value": 80,
"type": 11,
"unit": 0
}
]
},
{
"grpid": 179089150,
"attrib": 0,
"date": 1391167537,
"category": 1,
"measures": [
{
"value": 69,
"type": 9,
"unit": 0
},
{
"value": 112,
"type": 10,
"unit": 0
},
{
"value": 67,
"type": 11,
"unit": 0
}
]
},
{
"grpid": 179079661,
"attrib": 2,
"date": 1391164672,
"category": 1,
"measures": [
{
"value": 720,
"type": 1,
"unit": -1
}
]
},
{
"grpid": 17998560,
"attrib": 2,
"date": 146989672,
"category": 1,
"measures": [
{
"value": 284,
"type": 4,
"unit": -2
}
]
}
]
}
}
It seems, you want to deserialize your json string, not serialize:
var obj = JsonConvert.DeserializeObject<Withings.RootObject>(json);
public class Withings
{
public class Measure
{
public int value { get; set; }
public int type { get; set; }
public int unit { get; set; }
}
public class Measuregrp
{
public int grpid { get; set; }
public int attrib { get; set; }
public int date { get; set; }
public int category { get; set; }
public List<Measure> measures { get; set; }
}
public class Body
{
public int updatetime { get; set; }
public List<Measuregrp> measuregrps { get; set; }
}
public class RootObject
{
public int status { get; set; }
public Body body { get; set; }
}
}
JsonConvert.SerializeObject(myString) takes an object an returns a string. If you want to turn a string into an object you want to use Deserialize<T>(sting json). Given the arguments name in your sample is myString I would assume you're using the method wrong.
To deserialize you need an equivalent type like;
public class myObject
{
public int status { get; set; }
public Body body { get; set; }
}
public class Body
{
//other parameters ect
}
Your object model needs to exactly match the json in order by Deserialize<T> to behave correctly.

How to handle error in Json.Net parsing

I'm using Json.Net for Json deserialization. On occasion the Json string I read is not correct (which I can't fix since I don't produce it). In particular, at one specific place, where there should be a string, sometimes there is a serialized object instead. Json.Net then complains, not surprisingly, about finding an object where it expected a string.
I found I can intercept this by using Error in JsonSerializerSettings and also make Json.Net ignore the problem by setting ErrorContext.Handled. But I want to do even more. If I could look at the serialised object I could figure out what the string should be and in theory supply the correct answer. In practice I can't figure our how to do so. Especially, in the error handler:
How do I access the string that the parser tripped up on (Note that the parser can successfully continue if ErrorContext.Handled is set, so it can determine start and end of the problem string correctly)?
How do I supply the correct string back to the parser, or alternatively get access to the currently parsed object so I can set the value manually?
[Edit]
As requested a simplified example:
The incorrect Json string I get to parse:
{
"id": 2623,
"name": {
"a": 39,
"b": 0.49053320637463277,
"c": "cai5z+A=",
"name": "22"
},
"children": [
{
"id": 3742,
"name": {
"a": 37,
"b": 0.19319664789046936,
"c": "Me/KKPY=",
"name": "50"
},
"children": [
{
"id": 1551,
"name": {
"a": 47,
"b": 0.6935373953047849,
"c": "qkGkMwY=",
"name": "9"
},
"children": []
},
{
"id": 4087,
"name": {
"a": 5,
"b": 0.42905938319352427,
"c": "VQ+yH6o=",
"name": "84"
},
"children": []
},
{
"id": 614,
"name": {
"a": 19,
"b": 0.7610801005554758,
"c": "czjTK1s=",
"name": "11"
},
"children": []
}
]
},
{
"id": 3382,
"name": {
"a": 9,
"b": 0.36416331043660793,
"c": "lnoHrd0=",
"name": "59"
},
"children": [
{
"id": 4354,
"name": {
"a": 17,
"b": 0.8741648112769075,
"c": "CD2i2I0=",
"name": "24"
},
"children": []
},
{
"id": 2533,
"name": {
"a": 52,
"b": 0.8839575992356788,
"c": "BxFEzVI=",
"name": "60"
},
"children": []
},
{
"id": 5733,
"name": {
"a": 4,
"b": 0.7230552787534219,
"c": "Un7lJGM=",
"name": "30"
},
"children": []
}
]
},
{
"id": 9614,
"name": {
"a": 81,
"b": 0.4015882813379114,
"c": "dKgyRZk=",
"name": "63"
},
"children": [
{
"id": 7831,
"name": {
"a": 81,
"b": 0.2784254314743101,
"c": "xZur64o=",
"name": "94"
},
"children": []
},
{
"id": 6293,
"name": {
"a": 73,
"b": 0.32629523068959604,
"c": "lMkosP4=",
"name": "93"
},
"children": []
},
{
"id": 5253,
"name": {
"a": 13,
"b": 0.19240453242901923,
"c": "oOPZ3tA=",
"name": "5"
},
"children": []
}
]
}
]
}
And here to class to parse it into:
class Node
{
[JsonProperty]
int id;
[JsonProperty]
string name;
[JsonProperty]
List<Node> children;
}
As you can see it expects a string name but sometimes incorrectly gets a serialised object (which contains the string in question as a member). This only happens in some JSON strings but not others, so I can't just change the class definition of Node to match.
[Edit 2]
A "correct" Json string according to my API would look like this:
{
"id": 2623,
"name": "22",
"children": [
{
"id": 3742,
"name": "50",
"children": [
{
"id": 1551,
"name": "9",
"children": []
},
{
"id": 4087,
"name":"84",
"children": []
},
...
Trying to detect errors after the fact and then reparse from the point of the error is going to be problematic, as you have seen. Fortunately, the problem you've described can be solved in a straightforward manner using a custom JsonConverter. The idea is to have the converter read the data into a temporary structure that can handle either form (object or string), query the type, then construct the Node from there.
Here is the code for the converter:
class NodeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Node));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Node node = new Node();
node.id = (int)jo["id"];
JToken name = jo["name"];
if (name.Type == JTokenType.String)
{
// The name is a string at the current level
node.name = (string)name;
}
else
{
// The name is one level down inside an object
node.name = (string)name["name"];
}
node.children = jo["children"].ToObject<List<Node>>(serializer);
return node;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, add a [JsonConverter] attribute to your Node class like this:
[JsonConverter(typeof(NodeConverter))]
class Node
{
public int id { get; set; }
public string name { get; set; }
public List<Node> children { get; set; }
}
Then you can deserialize as normal:
Node node = JsonConvert.DeserializeObject<Node>(json);
Here is a full demo showing the converter in action. For illustration purposes, I've created a new JSON string that contains a combination of the "good" and "bad" nodes you described in your question.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""id"": 2623,
""name"": {
""a"": 39,
""b"": 0.49053320637463277,
""c"": ""cai5z+A="",
""name"": ""22""
},
""children"": [
{
""id"": 3741,
""name"": ""50"",
""children"": [
{
""id"": 1550,
""name"": ""9"",
""children"": []
},
{
""id"": 4088,
""name"": {
""a"": 5,
""b"": 0.42905938319352427,
""c"": ""VQ+yH6o="",
""name"": ""85""
},
""children"": []
}
]
},
{
""id"": 3742,
""name"": {
""a"": 37,
""b"": 0.19319664789046936,
""c"": ""Me/KKPY="",
""name"": ""51""
},
""children"": [
{
""id"": 1551,
""name"": {
""a"": 47,
""b"": 0.6935373953047849,
""c"": ""qkGkMwY="",
""name"": ""10""
},
""children"": []
},
{
""id"": 4087,
""name"": ""84"",
""children"": []
}
]
}
]
}";
Node node = JsonConvert.DeserializeObject<Node>(json);
DumpNode(node, "");
}
private static void DumpNode(Node node, string indent)
{
Console.WriteLine(indent + "id = " + node.id + ", name = " + node.name);
foreach(Node child in node.children)
{
DumpNode(child, indent + " ");
}
}
}
Output:
id = 2623, name = 22
id = 3741, name = 50
id = 1550, name = 9
id = 4088, name = 85
id = 3742, name = 51
id = 1551, name = 10
id = 4087, name = 84

Categories