Collection Navigation Properties returning odd JSON hierarchy - c#

I have 2 classes:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public B myB { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<A> myAs { get; set; }
}
Im using Postman to test the Api calls.
public IEnumerable<B> GetBs()
{
return _context.Bs.Include(b => b.myAs).ToList();
}
returns as expected, B objects and a list of their associated A objects:
{
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
},
{
"Id": 2,
"Name": "A2"
}
]
},
{
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 3,
"Name": "A3"
},
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
The reverse however returns a wierd hierarchical structure:
public IEnumerable<A> GetAs()
{
return _context.As.Include(a => a.myB).ToList();
}
returns:
[
{
"Id": 1,
"Name": "A1",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 2,
"Name": "A2"
}
]
}
},
{
"Id": 2,
"Name": "A2",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
}
]
}
},
{
"Id": 3,
"Name": "A3",
"myB": {
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
}
]
The GetAs method returns A objects with B objects with further nested A objects.
My understanding after a little research (I could be very wrong here), is that because A has a navigation property to B (myB) and B has a navigation property to a list of A objects (myAs) this is causing some kind of loop.
My questions are
Is my understanding here correct ? Is this why the hierarchy is returning in this weird layout ?
How do i fix this ? I can take the ICollection navigation property out of the domain model but then I can no longer query As and their associated Bs ?
note A and B are not actually my domain models. I just wanted to keep the example as simple as possible.
Thanks in advance.

A few things here:
The output has the expected shape. As you suspect, the bidirectional references are being expanded by the serializer. Think of what would happen if you manually serialized each property of each object recursively. That is what happening.
To solve the immediate problem configure you default serializer settings like this:
jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.Serialization.ReferenceLoopHandling.Ignore;
The above is useful when prototyping but when your application is more formalized, you should create and return dedicated view model types from your web API endpoints.
public class AViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<AViewModel> myAs { get; set; }
}
public IEnumerable<BViewModel> GetBs()
{
return _context.Bs.Include(b => b.myAs)
.Select(b => new BViewModel
{
Id = b.Id,
Name = b.Name,
As = b.As.Select(a => new AViewModel
{
Id = a.Id,
Name = a.Name
})
})
.ToList();
}
It is worth noting that there are libraries, such as the well regarded AutoMapper, that can perform these translations between model types for you, automatically assigning corresponding properties by name using reflection.
Personally, I try to avoid reflection based approaches as much as possible as they tend to render code difficult to reason about statically. This hinders both human readers such as ourselves and tools like the C# language.
That said, it can be worth the tradeoff depending on the task at hand. I hope to eventually see language level support that eliminates such boilerplate assignments without stepping into the realm of stringiness but I have a long time to wait.

Related

Deserialize subset of object in array with C#

I'm trying to find an elegant way in C# using System.Text.Json to deserialize a list of objects that were serialized (from C++ with cereal) with the following JSON data:
{
"value":
[
{
"ptr_wrapper": {
"id": 2147483649,
"data": {
"cereal_class_version": 6,
"ID": 1,
"name": "CWC",
"shadowCompetition": {
"ptr_wrapper": {
"id": 0
}
},
"userPlayable": true
}
}
},
{
"ptr_wrapper": {
"id": 2147483650,
"data": {
"ID": 20,
"name": "NAME2",
"shadowCompetition": {
"ptr_wrapper": {
"id": 0
}
},
"userPlayable": true
}
}
}
]
}
and deserialize this into a list for the following class:
class Competition
{
public int ID { get; set; }
public string name { get; set; }
public bool userPlayable { get; set; }
public Competition? shadowCompetition { get; set; }
}
I can skip the first node "value" easily. But then, the "ptr_wrapper" node encapsulates the "data" node that contains the data I really want to get.
I believe that a JsonConverter may be the answer, but I'm really struggling to find a solution.

How to loop through nested JSON objects in C#?

I am working on a C# project where I need to call an API and parse through the JSON response.
The JSON response:
{
"version": "3.9.1",
"data": {
"obj1": {
"version": "1.0.0",
"id": "obj1",
"name": "Object 1",
"title": "Object 1",
"info": {
"info1": 8,
"info2": 4,
"info3": 3
},
"image": {
"full": "Object1.png",
"w": 64,
"h": 64
},
"tags": [
"Tag1",
"Tag2"
]
},
"obj2": {
"version": "1.0.0",
"id": "obj2",
"name": "Object 2",
"title": "Object 2",
"info": {
"info1": 1,
"info2": 6,
"info3": 7
},
"image": {
"full": "Object2.png",
"w": 64,
"h": 64
},
"tags": [
"Tag1",
"Tag6"
]
},
"obj3": {
"version": "1.0.0",
"id": "obj3",
"name": "Object 3",
"title": "Object 3",
"info": {
"info1": 0,
"info2": 1,
"info3": 2
},
"image": {
"full": "Object3.png",
"w": 64,
"h": 64
},
"tags": [
"Tag7",
"Tag8"
]
}
}
}
With this response, I want to loop through all the objects located inside data. The number of objects inside data isn't always the same; it is possible that there will only be one object or ten objects.
The problem I ran into is that I can't loop through the data property because of the following error:
foreach statement cannot operate on variables of type 'Objects' because 'Objects' does not contain a public instance or extension definition for 'GetEnumerator'
I know why I am getting the error, but I can't find a way of fixing my problem.
My code:
MainWindow.xaml.cs
WebRequest request = WebRequest.Create(path);
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string ReaderJson = reader.ReadToEnd();
var JsonResponse = JsonConvert.DeserializeObject<Response>(ReaderJson);
foreach (var obj in JsonResponse.data)
{
Console.WriteLine(obj.ToString());
}
JsonParser.cs
public class Response
{
public string version { get; set; }
public Objects data { get; set; }
}
public class Objects
{
public string id { get; set; }
public string name { get; set; }
public string title { get; set; }
public Info info { get; set; }
public Images images { get; set; }
}
public class Info
{
public int info1 { get; set; }
public int info2 { get; set; }
public int info3 { get; set; }
}
public class Images
{
public string full { get; set; }
public int w { get; set; }
public int h { get; set; }
}
How can I loop through all the objects inside data without calling them using obj1, obj2, etc.?
The problem here is that your JSON schema defines a dictionary for data, but your data property returns a single instance of your Objects class.
Data Model
What you want is for your Response class to look something like this:
public class Response
{
public string version { get; set; }
public Dictionary<string, Objects> data { get; set; }
}
Everything else should be fine.
Looping Through Data
Given the above, you can now loop through the data dictionary using something like:
foreach (var obj in JsonResponse.data)
{
Console.WriteLine(obj.Key);
}
Note: When looping through a Dictionary<TKey, TValue>, an enumeration of KeyValuePair<TKey, TValue>s will be returned, with your key (i.e., obj1) in the Key property, and your object in the Value property.
Naming Conventions
Obviously, the name Objects doesn't makes sense here, since it represents a single object, not a collection of objects. I'm keeping it Objects for consistency with your code, but you'll want to give it a singular name. I assume that was just left over from you wanting it to represent a collection.

Deserialize JSON with Newtonsoft

I'm having problems with this JSON to use it in combobox and datagridview. I'm using Newtonsoft to do it:
{
"users": [
{
"id": 1,
"name": "Test 1",
"email": "test1#test.com",
"events": [
{
"id": 1,
"name": "Event 1",
"date": "11/10/2019",
"finish": 0
},
{
"id": 2,
"name": "Event 2",
"date": "12/10/2019",
"finish": 0
}
]
},
{
"id": 2,
"name": "Test 2",
"email": "test2#test.com",
"events": [
{
"id": 2,
"name": "Event 2",
"date": "17/10/2019",
"finish": 0
}
]
}
]
}
And this is the class (JsonEvent.cs). Generated with json2csharp.com:
using System;
using System.Collections.Generic;
namespace TestDO.Models
{
class JsonEvent
{
public partial class Admin
{
public List<User> Users { get; set; }
}
public partial class User
{
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<Event> Events { get; set; }
}
public partial class Event
{
public long Id { get; set; }
public string Name { get; set; }
public string Date { get; set; }
public long Finish { get; set; }
}
}
}
But now, I do not know how to use the result with combobox datasource or a datagrid.
So far the only time I've worked with this has been for a much simpler topic like this (this is and example, It is not related to this):
JsonResponde.cs
namespace TestDO.Models
{
class JsonResponse
{
public string Upgraded { get; set; }
public string time{ get; set; }
}
}
And then I check the result of Upgraded like this:
var jResponse = JsonConvert.DeserializeObject<JsonResponse>(json);
if (jResponse.Upgraded == "true")
But I don't know how to do it with a more complex json.
I want to use event id for combobox and event name for display.
And for datagrid, user id, user name, event name, event date for each line.
Thank you in advance for any help to solve the problem.
Your C# representation of the JSON you provided seems to perfectly fine and matching. Therefor you should be able to do something like the following:
var jResponse = JsonConvert.DeserializeObject<Admin>(json);
If the JSON array Users is not nested in another object you could also convert it to a List<User> (HashSet or similar collections should also work), if it's the only property of your Admin class. This would look something like this:
var jResponse = JsonConvert.DeserializeObject<List<User>>(json);
For this to work your JSON would need to look something like this:
[
{
"id": 1,
"name": "Test 1",
"email": "test1#test.com",
"events": [
{
"id": 1,
"name": "Event 1",
"date": "11/10/2019",
"finish": 0
},
{
"id": 2,
"name": "Event 2",
"date": "12/10/2019",
"finish": 0
}
]
},
{
"id": 2,
"name": "Test 2",
"email": "test2#test.com",
"events": [
{
"id": 2,
"name": "Event 2",
"date": "17/10/2019",
"finish": 0
}
]
}
]
Edit
If you want to feed your users to a combo-box, you can just pass the list to the combo-box. See the example below:
ComboBox cb = new ComboBox();
cb.DataSource = jResponse.Users.SelectMany(x => x.Events.OrderBy(y => y.Id).Select(y => y.Name));
Of course your Users object contains a lot of information, that is just an assumption, that you want to have all the names of the users in the ComboBox.
You need to deserialize your json to an object of the Admin class you created, which you can do like this:
var AdminObj = JsonConvert.DeserializeObject<Admin>(json);
From there, you have access your Users list by AdminObj.Users, which you can loop through to get whatever data you need for your application.

Deserialize nested JSON array in C#

I have a JSON array with nested objects, representing a menu, as this:
[
[
{
"name": "Item 1",
"id": 1
},
{
"name": "Item 2",
"id": 2,
"children": [
[
{
"name": "Item 21",
"id": 21
}
]
]
},
{
"name": "Item 3",
"id": 3,
"children": [
[
{
"name": "Item 31",
"id": 31,
"children": [
[
{
"name": "Item 311",
"id": 311
},
{
"name": "Item 312",
"id": 312
}
]
]
},
{
"name": "Item 32",
"id": 32
},
...
And I want to deserialize it using JavaScriptSerializer. I have some code as shown below but is not working.
var serializer = new JavaScriptSerializer();
var objects = serializer.Deserialize<Menu>(jsonData);
...
public class Menu
{
public int id { get; set; }
public string name { get; set; }
public Menu[] children { get; set; }
}
The error I get is "The type 'Menu' is not supported to deserialize a matrix".
I would appreciate any help on how to declare the custom object.
Cheers.
Your root object is a 2d jagged array of objects. The properties "children" are also 2d jagged arrays. Thus your Menu class needs to be:
public class Menu
{
public int id { get; set; }
public string name { get; set; }
public Menu [][] children { get; set; }
}
And deserialize your JSON as follows:
var serializer = new JavaScriptSerializer();
var objects = serializer.Deserialize<Menu [][]>(jsonData);
Alternatively, if you prefer lists to arrays, do:
public class Menu
{
public int id { get; set; }
public string name { get; set; }
public List<List<Menu>> children { get; set; }
}
And then
var objects = serializer.Deserialize<List<List<Menu>>>(jsonData);
Could the issue be that the actual data is an array but you're telling it to expect just one Menu?

How to deserialize JSON objects to List<> when the items are not in a Json array? (C#)

I'm using a Json string from another system. It looks something like this:
{
"BoolValue": true,
"Inventory": {
"Item1": {
"id": "1",
"name": "Item One"
},
"Item2": {
"id": "2",
"name": "Item Two"
},
"Item3": {
"id": "2",
"name": "Item Three"
}
}
}
How would I deserialize the "Item" objects to a List?
I know it's easy then the json uses an array for "Inventory": [] but how will I do it when it's just object after object under the Inventory property?
If I'm understanding correctly, you'll need a class setup like this:
public class Results {
public bool BoolValue { get; set; }
public Dictionary<string, Item> Inventory { get; set; }
}
public class Item {
public string id { get; set; }
public string name { get; set; }
}

Categories

Resources