C# Extract JSON from nested array - c#

I'm trying to iterate through nested JSON arrays using C# and JSON.NET. The JSON represents categories for an online webstore - below is an example. My goal is to create a list of all of the names of categories.
{
"id": 2,
"parent_id": 1,
"name": "Main Category List",
"is_active": true,
"position": 1,
"level": 1,
"product_count": 0,
"children_data": [
{
"id": 9,
"parent_id": 2,
"name": "Mens Clothing",
"is_active": true,
"position": 6,
"level": 2,
"product_count": 0,
"children_data": []
},
{
"id": 8,
"parent_id": 2,
"name": "Womens Clothing",
"is_active": true,
"position": 7,
"level": 2,
"product_count": 0,
"children_data": [
{
"id": 223,
"parent_id": 8,
"name": "Outdoor Clothing",
"is_active": true,
"position": 1,
"level": 3,
"product_count": 0,
"children_data": []
},
{
"id": 224,
"parent_id": 8,
"name": "Hiking Clothing",
"is_active": true,
"position": 2,
"level": 3,
"product_count": 0,
"children_data": []
},
{
"id": 596,
"parent_id": 8,
"name": "Dresses",
"is_active": true,
"position": 3,
"level": 3,
"product_count": 0,
"children_data": [
{
"id": 694,
"parent_id": 596,
"name": "Summer Dresses",
"is_active": true,
"position": 13,
"level": 4,
"product_count": 0,
"children_data": [
{
"id": 720,
"parent_id": 694,
"name": "Accessories",
"is_active": true,
"position": 1,
"level": 5,
"product_count": 0,
"children_data": [ ]
}
]
}
]
}
]
},
{
"id": 10,
"parent_id": 2,
"name": "Sale & Clearance",
"is_active": true,
"position": 8,
"level": 2,
"product_count": 0,
"children_data": []
}
]
}
There could be varying levels of categories and I need to parse every one. I want to get every category and create a map. For example (Main Category List --> Women's Clothing --> Outdoor Clothing). I'm thinking I can check the depth of children data but I don't know how to keep checking deeper and deeper into the next Json object.
JObject responseObject = JObject.Parse(response.Content);
foreach (JObject category in getCatResponseObj.SelectToken("children_data"))
{
while loop checking depth of children_data
}

Were it me, I would create the most complete JSON file I can come up with (including all possible entries), then use json2csharp or an equivalent tool to create c# classes then deserialize the Json and work with it natively. You may have to massage the results a bit - but I think you can get there. If that didn't work, I would use Newtonsofts JSON LINQ Features (documentation). JToken gives you parent / children combinations that allows you to traverse up and down the tree. The samples in the documentation are really good, so no need of my filling up the page with duplicate info.
(Generated from your example)
public class ChildrenData
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public bool is_active { get; set; }
public int position { get; set; }
public int level { get; set; }
public int product_count { get; set; }
public List<object> children_data { get; set; }
}
public class RootObject
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public bool is_active { get; set; }
public int position { get; set; }
public int level { get; set; }
public int product_count { get; set; }
public List<ChildrenData> children_data { get; set; }
}

This appears to be a recursively defined structure. You should create a function to extract the values of each of the children so you could reuse it recursively.
IEnumerable<string> GetCategoryNames(JObject data)
{
yield return (string)data["name"];
foreach (var name in data["children_data"].Cast<JObject>().SelectMany(GetCategoryNames))
yield return name;
}
Then call it on the root object to get your names putting in a list or whatever.
var obj = JObject.Parse(response.Content);
var names = GetCategoryNames(obj).ToList();
Otherwise, if you want to indiscriminately get all names in the object tree, just keep in mind that SelectToken()/SelectTokens() also takes json path queries. So to get all names of all descendants, you'd just do this:
let names = obj.SelectTokens("..name").ToList();

Related

Why is dotnet not reading my request body correctly?

I have a ForumController where I have a function ChangeOrder with a ChangeOrderDto. Here is the code:
[HttpPost("change-order")]
public async Task<ActionResult> ChangeOrder([FromBody] ChangeOrderDto[] forums)
{
foreach (var dto in forums)
{
var forum = await context.Forums.FindAsync(dto.Id);
if (forum is not null)
{
forum.Order = dto.Order;
forum.ParentId = dto.ParentId;
}
}
await context.SaveChangesAsync();
return Ok();
}
public class ChangeOrderDto
{
public int Id { get; set; }
public int Order { get; set; }
public int ParentId { get; set; }
}
However when I post to /api/forum/change-order with json
{
"forums": [
{id: 3, order: 1, parent_id: 1},
{id: 4, order: 2, parent_id: 1}
]
}
I get this error in response:
Validation failed, forums field is required.
I tried adding [FromBody("forums")] but it did not work.
If I understand your requirement correctly, your JSON should look like this:
[
{"id": 3, "order": 1, "parent_id": 1},
{"id": 4, "order": 2, "parent_id": 1}
]
You may need to change "parent_id" to "parentId" as well since you don't specify any custom property name for the JSON parser.
It then would look like this (also formatted for legibility):
[
{
"id": 3,
"order": 1,
"parentId": 1
},
{
"id": 4,
"order": 2,
"parentId": 1
}
]
If you want to validate your JSON, you can also use a service like this one: https://jsonformatter.curiousconcept.com/
Extrapolating on the answers from below your post (Jon Skeet and ewerspej), lets build this payload:
First, You want to send an array of data:
[
]
This array have in your case 2 objects:
[
{
},
{
}
]
Objects you transferring have some properties with values:
[
{
"id": 3,
"order": 1,
"parentId": 1
},
{
"id": 4,
"order": 2,
"parentId": 1
}
]
Take a look especially at the name of parrentId property - I think this should be named like this in the payload instead of parent_id that You used in your payload.

EF Core Customize JSON Serialization with LINQ

I am building out a demo for an API in .NET Core and constructing a nested JSON object that needs to get constructed through a series of LINQ queries.
My current issue is that when i get about 4 layers deep, I want to customize what actually gets serialized, more specifically, i want to "Not Include" specific navigation properties for this query specifically, but not for general purpose, only for this specific query.
My first thought was to do a DTO, but that seems like an unnecessary extra model just for this one specific case...i'd like to work with LINQ directly to manipulate for my result.
I have already added the following to my Startup.cs file to avoid cycles:
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
I have 5 models currently in action that look like this:
public class Hotel
{
public int ID { get; set; }
public string Name { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Phone { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class Room
{
public int ID { get; set; }
public string Name { get; set; }
public Layout Layout { get; set; }
public List<RoomAmenities> RoomAmenities { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class HotelRooms
{
public int HotelID { get; set; }
public int RoomNumber { get; set; }
public int RoomID { get; set; }
public decimal Rate { get; set; }
public bool PetFriendly { get; set; }
public Hotel Hotel { get; set; }
public Room Room { get; set; }
}
public class Amenities
{
public int ID { get; set; }
public string Name { get; set; }
// Navigation Properties
public List<RoomAmenities> RoomAmenities { get; set; }
}
public class RoomAmenities
{
public int RoomID { get; set; }
public int AmenitiesID { get; set; }
public Room Room { get; set; }
public Amenities Amenity { get; set; }
}
In my services, i have this logic that is actually doing the querying:
public async Task<Hotel> GetById(int id)
{
var hotel = await _context.Hotel.FindAsync(id);
// Get a list of rooms
var rooms = await _context.HotelRooms.Where(r => r.HotelID == id)
.Include(d => d.Room)
.ThenInclude(a => a.RoomAmenities)
.ThenInclude(x => x.Amenity).ToListAsync();
hotel.HotelRooms = rooms;
return hotel;
}
The current output is:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true
}
]
}
}
]
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false
}
]
}
}
]
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": []
}
}
]
}
This is much too nested for me, i'd like the inner "roomAmenities" inside the "amenity" object to not be included. I'd like my object to look like this:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar"
}
}
],
"hotelRooms": []
}
}
]
}
Does anyone have any guidance on how i can achieve this with EFCore and LINQ?
you can check this post
What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?
I see that you have already implemented referenceloopHandling, but even when you are using that you can only make the inner reference as null but not totally remove the key- value pair from json

How do I deserialize a tree of objects into a DTO?

I am attempting to deserialize a tree of JSon objects I get back into a DTO. The tree can be up to 4 levels deep, and over 1200 nodes. The code is C# using Newtonsoft.Json for deserialization.
EDIT
The JSon looks like:
[
{
"id": 1095,
"name": "Item1-1",
"children": [
{
"id": 1097,
"name": "Item2-2",
"children": [
{
"id": 18,
"name": "Item3-3",
"children": [
{
"id": 19,
"name": "Item4-4",
"children": [],
"level": 4,
"parentId": 18
},
{
"id": 20,
"name": "Item5-4",
"children": [],
"level": 4,
"parentId": 18
}
],
"level": 3,
"parentId": 1097
}
],
"level": 2,
"parentId": 1095
}
],
"level": 1,
"parentId": null
}
]
My DTO is similar to this:
public class MyDTO
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("children")]
public MyDTO[] Children { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("parentId")]
public int ParentId { get; set; }
public MyDTO()
{
}
}
I like Brian Rogers' solution in this link, which deserializes the json into objects:
How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?
public static class JsonHelper
{
public static object Deserialize(string json)
{
return ToObject(JToken.Parse(json));
}
private static object ToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
return token.Children<JProperty>()
.ToDictionary(prop => prop.Name,
prop => ToObject(prop.Value));
case JTokenType.Array:
return token.Select(ToObject).ToList();
default:
return ((JValue)token).Value;
}
}
}
I call the JSonHelper with object obj = JsonHelper.Deserialize(jsonString);
I have been experimenting with converting this code into a method to convert to MyDTO, but I keep running into compiler errors. I have attempted to simply convert the JValue casts with MyDTO and move on to the List with a List.
My goal is to call MyDTO obj = JsonHelp.Deserialize(jsonString) and get the tree of MyDTO objects back. Is it possible to do what I am trying, or should I find a way to cast each object to MyDTO?
First your JSON is missing a brakcet ], then your model needs a nullable parentId. Making a couple simple changes you can then you JsonConvert.DeserializeObject<IEnumerable<MyDTO>>(json).
So your JSON should look like this:
[
{
"id": 1095,
"name": "Item1-1",
"children": [
{
"id": 1097,
"name": "Item2-2",
"children": [
{
"id": 18,
"name": "Item3-3",
"children": [
{
"id": 19,
"name": "Item4-4",
"children": [],
"level": 4,
"parentId": 18
},
{
"id": 20,
"name": "Item5-4",
"children": [],
"level": 4,
"parentId": 18
}
],
"level": 2,
"parentId": 1095
}],
}],
"level": 1,
"parentId": null
}
]
And can be deserialized with this model:
public class MyDTO
{
public int Id { get; set; }
public string Name { get; set; }
public MyDTO[] Children { get; set; }
public int Level { get; set; }
public int? ParentId { get; set; }
}
Using this code:
var dto = JsonConvert.DeserializeObject<IEnumerable<MyDTO>>(json);

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.

Cannot deserialize the current JSON array

First: I'm new to using JSON, and I used the answers on here to use Json.Net to deserialize data from a Pokemon API into a C# class (Pokemon class). I used http://json2csharp.com to help me define my class and it looks like this:
public class Pokemon
{
public Pokemon(string json)
{
JsonConvert.PopulateObject(json, this, PokeApi.JsonSerializerSettings);
}
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("evolutions")]
public Evolutions evolutions { get; set; }
[JsonProperty("national_id")]
public int national_id { get; set; }
}
with a bunch of other properties like resource_uri, attack stat, etc.
As the answer offered on the aforementioned link said, I used JsonConvert.DeserializeObject(json) like so:
public Pokemon GetPokemon(int nationalId)
{
using (WebClient client = new WebClient())
{
var json = client.DownloadString("http://pokeapi.co/api/v1/pokemon/" + nationalId + "/");
var output = JsonConvert.DeserializeObject<Pokemon>(json);
return output;
}
}
However I keep getting an exception that says "Cannot deserialize the current JSON array (e.g.[1,2,3]) into type 'Evolutions' because the type requires a JSON object..."
I found a lot of other questions asking the same exact thing, but I was confused with the top answers - sometimes the answer was to use JsonProperty, sometimes it was to use JsonConverter, without really an explanation on what all these meant. Do I need both?
Edit: sample json (call: http://pokeapi.co/api/v1/pokemon/1/)
{
"abilities": [
{
"name": "overgrow",
"resource_uri": "/api/v1/ability/1/"
},
{
"name": "chlorophyll",
"resource_uri": "/api/v1/ability/2/"
}
],
"attack": 49,
"catch_rate": 45,
"created": "2013-11-02T12:08:25.745455",
"defense": 49,
"egg_cycles": 21,
"egg_groups": [
{
"name": "Monster",
"resource_uri": "/api/v1/egg/1/"
},
{
"name": "Grass",
"resource_uri": "/api/v1/egg/8/"
}
],
"ev_yield": "1 Sp Atk",
"evolutions": {
"level": 16,
"method": "level up",
"resource_uri": "/api/v1/pokemon/2/",
"to": "Ivysaur"
},
"exp": 64,
"growth_rate": "ms",
"happiness": 70,
"height": "2'4",
"hp": 45,
"male_female_ratio": "87.5/12.5",
"modified": "2013-11-02T13:28:04.914889",
"moves": [
{
"learn_type": "other",
"name": "Tackle",
"resource_uri": "/api/v1/move/1/"
},
{
"learn_type": "other",
"name": "Growl",
"resource_uri": "/api/v1/move/2/"
},
{
"learn_type": "level up",
"level": 10,
"name": "Vine whip",
"resource_uri": "/api/v1/move/3/"
}
],
"name": "Bulbasaur",
"national_id": 1,
"resource_uri": "/api/v1/pokemon/4/",
"sp_atk": 65,
"sp_def": 65,
"species": "seed pokemon",
"speed": 45,
"total": 318,
"types": [
{
"name": "grass",
"resource_uri": "/api/v1/type/5/"
},
{
"name": "poison",
"resource_uri": "/api/v1/type/8/"
}
],
"weight": "15.2lbs"
}
Evolutions class:
public class Evolutions
{
public int level { get; set; }
public string method { get; set; }
public string resource_uri { get; set; }
public string to { get; set; }
}
I tried http://json2csharp.com/ with http://pokeapi.co/api/v1/pokemon/19/ and what I see is
public class RootObject
{
//...
public List<Evolution> evolutions { get; set; }
//...
}
This pretty mush is your Pokemon class. So you need to declare Evolutions as list.

Categories