deserialize API response with different json value in the result - c#

I'm querying an external service and wanted to deserialize the response into a customer object but the issue is response for each customer may be different. some customer may have Sales entity in the response and few may have Marketing.
The json property for sales entity is SalesId and for marketing is MarketingId. Can you advise whether the model I use to store result is correct or any improvement ? If so, how would I deserialize the response without knowing the correct json property ?
For Customer 66666
{
"customerId": "66666",
"customerName": "test1234",
"dependentEntity": [
{
"SalesId": "3433434",
"SalesPersonName": "343434",
"SaleSource": "StorePurchase"
}
]
}
For Customer 5555
{
"customerId": "55555",
"customerName": "test2",
"dependentEntity": [
{
"MarketingId": "3433434",
"MarketingAppName": "343434",
"MarketingSource": "Online"
}
]
}
Here is the Model I'm thinking but not sure the correct one
public class Customer
{
public string customerId { get; set; }
public string customerName { get; set; }
public IList<T> dependentList { get; set; }
}
public class Dependent
{
[JsonProperty("Id")]
public string Id { get; set; }
public string Name { get; set; }
public string Source { get; set; }
}

You could probably try something like the following one:
public class DependentEntity
{
[JsonProperty("SalesId")]
public string SalesId { get; set; }
[JsonProperty("SalesPersonName")]
public string SalesPersonName { get; set; }
[JsonProperty("SaleSource")]
public string SaleSource { get; set; }
[JsonProperty("MarketingId")]
public string MarketingId { get; set; }
[JsonProperty("MarketingAppName")]
public string MarketingAppName { get; set; }
[JsonProperty("MarketingSource")]
public string MarketingSource { get; set; }
}
public class Customer
{
[JsonProperty("customerId")]
public string CustomerId { get; set; }
[JsonProperty("customerName")]
public string CustomerName { get; set; }
[JsonProperty("dependentEntity")]
public IList<DependentEntity> DependentEntity { get; set; }
}
We have a type for DependentEntity that has both the attributes of Marketing and Sales object. After parsing your input, you could create a logic (checking the attributes) based on which you could check if a DependentEntity is a Marketing or a Sales object.
The above classes was generated using, jsonutils.

If we can assume that the dependentEntity contains only a single type of objects then you can use json.net's schema to perform branching based on the matching schema.
So, lets suppose you have these dependent entity definitions:
public class DependentMarket
{
public string MarketingId { get; set; }
public string MarketingAppName { get; set; }
public string MarketingSource { get; set; }
}
public class DependentSales
{
public string SalesId { get; set; }
public string SalesPersonName { get; set; }
[JsonProperty("SaleSource")]
public string SalesSource { get; set; }
}
...
Then you can use these classes to generate json schemas dynamically:
private static JSchema marketSchema;
private static JSchema salesSchema;
//...
var generator = new JSchemaGenerator();
marketSchema = generator.Generate(typeof(DependentMarket));
salesSchema = generator.Generate(typeof(DependentSales));
And finally you can do the branching like this:
var json = "...";
var semiParsedJson = JObject.Parse(json);
JArray dependentEntities = (JArray)semiParsedJson["dependentEntity"];
JObject probeEntity = (JObject)dependentEntities.First();
if (probeEntity.IsValid(marketSchema))
{
var marketEntities = dependentEntities.ToObject<List<DependentMarket>>();
...
}
else if (probeEntity.IsValid(salesSchema))
{
var salesEntities = dependentEntities.ToObject<List<DependentSales>>();
...
}
else if ...
else
{
throw new NotSupportedException("The provided json format is not supported");
}

Related

How to display JSON data (nested) in C#

I am fetching an API that returns a JSON response like this:
{
"id": 161635,
"rev": 1,
"fields": {
"System.Url": "http://google.com",
"System.Type": "Bug",
"System.State": "New",
"System.AssignedTo": {
"displayName": "John Doe"
}
}
}
I want to display the id and everything inside fields.
This is my model:
public class WorkItemDetail {
public int id { get; set; }
public Dictionary<string, object> fields {get;set;}
}
Here is the problem, I can display the id and everything in fields except for some reason, I can't show displayName
Here is what I doing:
#WorkItemDetailResponse.id
#WorkItemDetailResponse.fields["System.WorkItemType"];
#WorkItemDetailResponse.fields["System.State"];
#WorkItemDetailResponse.fields["System.AssignedTo"];
#WorkItemDetailResponse.fields["System.AssignedTo"]["displayName"]; <!-- does not work -->
#code{
WorkItemDetailResponse = JsonConvert.DeserializeObject<WorkItemDetail>(ResponseBody);
}
I am new to C# so I don't know why this line is not working
#WorkItemDetailResponse.fields["System.AssignedTo"]["displayName"]
Create your DTO structure as follows:
public class Fields
{
[JsonProperty("System.Url")]
public string SystemUrl { get; set; }
[JsonProperty("System.Type")]
public string SystemType { get; set; }
[JsonProperty("System.State")]
public string SystemState { get; set; }
[JsonProperty("System.AssignedTo")]
public SystemAssignedTo SystemAssignedTo { get; set; }
}
public class WorkItemDetail
{
public int id { get; set; }
public int rev { get; set; }
public Fields fields { get; set; }
}
public class SystemAssignedTo
{
public string displayName { get; set; }
}
here's fiddle: https://dotnetfiddle.net/F9Wppv
another way - using dynamic variable: https://dotnetfiddle.net/Goh7YY
You need to cast the object value to a JObject
((JObject)JsonConvert.DeserializeObject<WorkItemDetail>(json).fields["System.AssignedTo"])["displayName"]
JSON.Net will create a JObject if the property type is object and the value is a JSON object.
db<>fiddle

How to generate a JSON class with dynamic name

I don't know if there is an existing name for that case, but I'm trying to retrieve data from NASA API (https://api.nasa.gov/) and I have a simple challenge to catch a list of objects near earth. Here is the JSON response I have from the GET request I do to "https://api.nasa.gov/neo/rest/v1/feed?...."
{
"links": {
"next": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-07-04&end_date=2021-07-04&detailed=false&api_key=NjgpxgSbYHXyFSBI3HaOhRowtjMZgAKv2t4DMRym",
"prev": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-07-02&end_date=2021-07-02&detailed=false&api_key=NjgpxgSbYHXyFSBI3HaOhRowtjMZgAKv2t4DMRym",
"self": "http://www.neowsapp.com/rest/v1/feed?start_date=2021-07-03&end_date=2021-07-03&detailed=false&api_key=NjgpxgSbYHXyFSBI3HaOhRowtjMZgAKv2t4DMRym"
},
"element_count": 6,
"near_earth_objects": {
"2021-07-03": [
{
"links": {
"self": "http://www.neowsapp.com/rest/v1/neo/3701710?api_key=NjgpxgSbYHXyFSBI3HaOhRowtjMZgAKv2t4DMRym"
},
"id": "3701710",
"neo_reference_id": "3701710",
"name": "(2014 WF497)",
"nasa_jpl_url": "http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=3701710",
"absolute_magnitude_h": 20.23,
"estimated_diameter": {
"kilometers": {
}
And that's the way it is built in Visual Studio (using the Special Paste option for JSON)
public class NearEarthObject
{
public Links links { get; set; }
public int element_count { get; set; }
public Near_Earth_Objects near_earth_objects { get; set; }
}
public class Links
{
public string next { get; set; }
public string prev { get; set; }
public string self { get; set; }
}
public class Near_Earth_Objects
{
public _20210703[] _20210703 { get; set; }
}
public class _20210703
{
public Links1 links { get; set; }
public string id { get; set; }
public string neo_reference_id { get; set; }
public string name { get; set; }
public string nasa_jpl_url { get; set; }
public float absolute_magnitude_h { get; set; }
public Estimated_Diameter estimated_diameter { get; set; }
public bool is_potentially_hazardous_asteroid { get; set; }
public Close_Approach_Data[] close_approach_data { get; set; }
public bool is_sentry_object { get; set; }
}
The question is, inside of the element "near_earth_objects", there is an element called "2021-07-03" (the date of the data I requested), the problem is that I am trying to include it into a DataGridView made in .NET C# (Windows Forms, but that doesn't matters here, I think) and the user wants to get the information by date. So, "2021-07-03" is a valid member just for one day, and the user should be able to get data from multiple days.
So, is there a way in C# to get all child objects inside of near_earth_objects without knowing their names since there will be the option to search for asteroids from date X to Y in my application?
Using System.Text.Json
The API response will map to the following classes
public class Neo
{
public Links Links { get; set; }
public int ElementCount { get; set; }
public Dictionary<string, List<NearEarthObject>> NearEarthObjects { get; set; }
}
public class Links
{
public string Next { get; set; }
public string Prev { get; set; }
public string Self { get; set; }
}
public class NearEarthObject
{
public Links Links { get; set; }
public string Id { get; set; }
public string Name { get; set; }
// Other properties
}
The NearEarthObjects is simply a Dictionary, where the key is the formatted date and value is a List containing NearEarthObject
The PropertyNamingPolicy will allow us to support the API's underscore property naming convention.
public class UnderscoreNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
return name.Underscore();
}
}
Example usage
// using using System.Text.Json;
var response = await new HttpClient().GetStringAsync(url);
var neo = JsonSerializer.Deserialize<Neo>(response, new JsonSerializerOptions
{
PropertyNamingPolicy = new UnderscoreNamingPolicy()
});
foreach(var neos in neo.NearEarthObjects)
{
Console.WriteLine(neos.Key);
}
use System.Text.Json, JsonNamingPolicy
demo code
public class DynamicNamePolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
var today = DateTime.Today.ToString("yyyy-MM-dd");
if (name.Equals("DateData")) //model property name
return today; //convert to json string property name
return name;
}
}
//data deserialize
string data = ""; //json string
var obj = JsonSerializer.Deserialize<NearEarthObject>(data, new JsonSerializerOptions
{
PropertyNamingPolicy = new DynamicNamePolicy(),
});

JSON with Array Respone from API

i have a HTTPclient and im sending request to a Server.
The response from the server is a JSON. In this Json is also an array.
I want do display the information from the JSON in the consol.
I get all information from the JSON, but i dont get the array displayed in my console.
The JSON looks like this:
{
"Name1": "Karl",
"Name2": "Peter",
"Name3": "Wilhelm",
"PreNames": {
"PreName": "Werner",
"PreName2": "Josef"
}
}
So now my Code so far:
public class PreNames //for Array
{
public string PreName { get; set; }
public string PreName2{ get; set; }
}
public class Names//for JSON
{
public string Name1{ get; set; }
public string Name2{ get; set; }
public string Name3{ get; set; }
}
And my Main:
if (Response().IsSuccessStatusCode)
{
var namesJ= Response().Content.ReadAsAsync<IEnumerable<Names>>().Result;
var preNamesJ= await Response().Content.ReadAsAsync<List<PreNames>>();
foreach (var a in namesJ)
{
Console.WriteLine("Name1: {0}", a.Name1);
foreach(var b in preNamesJ)
{
Console.WriteLine("{0}", b.PreName);
}
}
So in the console are all names displayed, but not the PreNames so i cant get the PreNames from the array in the JSON...
I Hope somebody could help me :)
Add the PreNames property to your Names class and only read one time.
public class Names//for JSON
{
public string Name1{ get; set; }
public string Name2{ get; set; }
public string Name3{ get; set; }
public List<PreNames> PreNames { get; set;}
}
Once you read in the result one time, you can access the elements via the main object
var namesJ= Response().Content.ReadAsAsync<Names>().Result;
foreach(var names in namesJ.PreNames)
{
Console.WriteLine(names.PreName);
}
updated
Since you changed the json and made PreNames just an object instead of an array, simply access the property of related object.
public class Names//for JSON
{
public string Name1{ get; set; }
public string Name2{ get; set; }
public string Name3{ get; set; }
public PreNames PreNames { get; set;}
}
var namesJ= Response().Content.ReadAsAsync<Names>().Result;
Console.WriteLine(namesJ.PreNames.Name);
#Leonc443, Here is a working code
using Newtonsoft.Json; //add this nuget package
using System;
namespace ConsoleApp
{
class Program
{
public static void Main(string[] args)
{
string json = #"{
'Name1': 'Karl',
'Name2': 'Peter',
'Name3': 'Wilhelm',
'PreNames': {
'PreName': 'Werner',
'PreName2': 'Josef'
}
}"; // data received from api
var names = JsonConvert.DeserializeObject<Names>(json);
Console.WriteLine(names.Name1); // Karl
Console.WriteLine(names.Name2); //Peter
Console.WriteLine(names.Name3); //Wilhelm
Console.WriteLine(names.PreNames.PreName); //Werner
Console.WriteLine(names.PreNames.PreName2); // Josef
}
}
public class Names
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
public PreNames PreNames { get; set; }
}
public class PreNames
{
public string PreName { get; set; }
public string PreName2 { get; set; }
}
}
Please let me know if this helps
In case your Json looks like:
{
"Name1": "Karl",
"Name2": "Peter",
"Name3": "Wilhelm",
"PreNames": {
"PreName": "Werner",
"PreName2": "Josef"
}
}
If the PreNames are in the Names, you shuld add a PreNames field to the Names Class.
public class Names
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
public PreNames PreNames { get; set; }
}
Then you can read the inner PreNames:
foreach (var b in a.Prenames) {
Console.WriteLine(b.PreName);
}
But I'm not sure if I understand your question.
I don't have sufficient reputation to comment, so... I'll give it a try.
Edited after proper json updated in Answer.
Change your class Names
public class Names
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
public PreNames PreNames { get; set; }
}
Then deserialize it with JsonConvert
var json = await Response().Content.ReadAsStringAsync();
var names = JsonConvert.DeserializeObject<Names>(json);
Then access data
Console.WriteLine($"Name 1: {names.Name1}");
Console.WriteLine($"Prename 1: {names.PreNames.PreName}");

Deserialize JSON using specific properties

I'm trying to deserialize JSON without declaring every property in C#. Here is a cut-down extract of the JSON:
{
"resourceType": "export",
"type": "search",
"total": 50,
"timestamp": "2020-08-02T18:26:06.747+00:00",
"entry": [
{
"url": "test.com/123",
"resource": {
"resourceType": "Slot",
"id": [
"123"
],
"schedule": {
"reference": {
"value": "testvalue"
}
},
"status": "free",
"start": "2020-08-03T08:30+01:00",
"end": "2020-08-03T09:00+01:00"
}
}
]
}
I want to get the values out of entry → resource, id and start.
Any suggestions on the best way to do this?
I've made very good experiences with json2sharp. You can enter your JSON data there and it will generate the classes you need to deserialize the JSON data for you.
public class Reference
{
public string value { get; set; }
}
public class Schedule
{
public Reference reference { get; set; }
}
public class Resource
{
public string resourceType { get; set; }
public List<string> id { get; set; }
public Schedule schedule { get; set; }
public string status { get; set; }
public string start { get; set; }
public string end { get; set; }
}
public class Entry
{
public string url { get; set; }
public Resource resource { get; set; }
}
public class Root
{
public string resourceType { get; set; }
public string type { get; set; }
public int total { get; set; }
public DateTime timestamp { get; set; }
public List<Entry> entry { get; set; }
}
The next step is to choose a framework which will help you to deserialize. Something like Newtonsoft JSON.
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
If you want to get the data without declaring classes, you can use Json.Net's LINQ-to-JSON API (JToken, JObject, etc.). You can use the SelectToken method with a JsonPath expression to get what you are looking for in a couple of lines. Note that .. is the recursive descent operator.
JObject obj = JObject.Parse(json);
List<string> ids = obj.SelectToken("..resource.id").ToObject<List<string>>();
DateTimeOffset start = obj.SelectToken("..resource.start").ToObject<DateTimeOffset>();
Working demo here: https://dotnetfiddle.net/jhBzl4
If it turns out there are actually multiple entries and you want to get the id and start values for all of them, you can use a query like this:
JObject obj = JObject.Parse(json);
var items = obj["entry"]
.Children<JObject>()
.Select(o => new
{
ids = o.SelectToken("resource.id").ToObject<List<string>>(),
start = o.SelectToken("resource.start").ToObject<DateTimeOffset>()
})
.ToList();
Demo: https://dotnetfiddle.net/Qe8NB7
I am not sure why you don't deserialize the lot (even if it's minimally populated) since you have to do the inner classes anyway.
Here is how you could bypass some of the classes (1) by digging into the JObjects
Given
public class Reference
{
public string value { get; set; }
}
public class Schedule
{
public Reference reference { get; set; }
}
public class Resource
{
public string resourceType { get; set; }
public List<string> id { get; set; }
public Schedule schedule { get; set; }
public string status { get; set; }
public string start { get; set; }
public string end { get; set; }
}
public class Entry
{
public string url { get; set; }
public Resource resource { get; set; }
}
You could call
var results = JObject.Parse(input)["entry"]
.Select(x => x.ToObject<Entry>());

Can't deserialize an indexed name value pair child node C#

I'm running into an issue when I am trying to deserialize a webAPI GET call that returns a JSON object. The issue being one particular property is always null after being deserialized.
The JSON object looks like this:
{
"status":"OK",
"masterlist":{
"session":{
"session_id":intValue,
"session_name":"stringValue"
},
"0":{
"bill_id":intValue,
"number":"stringValue",
"change_hash":"stringValue",
"url":"stringValue",
"status_date":"dateValue",
"status":"stringValue",
"last_action_date":"dateValue",
"last_action":"stringValue",
"title":"stringValue",
"description":"stringValue"
},
"1":{
"bill_id":intValue,
"number":"stringValue",
"change_hash":"stringValue",
"url":"stringValue",
"status_date":"dateValue",
"status":"stringValue",
"last_action_date":"dateValue",
"last_action":"stringValue",
"title":"stringValue",
"description":"stringValue"
},
"2":{
"bill_id":intValue,
"number":"stringValue",
"change_hash":"stringValue",
"url":"stringValue",
"status_date":"dateValue",
"status":"stringValue",
"last_action_date":"dateValue",
"last_action":"stringValue",
"title":"stringValue",
"description":"stringValue"
}
}
}
As you can see the second property of masterlist isn't an array, that would make life too easy... But it looks more like a collection of name/value pairs. I have reviewed This post and the associated one listed within but they both pertain to if the name/value pair where at the root level where mine is not.
My method that I am using to deserialize is:
BillMaster billMasterList = new BillMaster();
using (HttpClient client = new HttpClient())
{
string json = Get("&op=getMasterList&state=TN");
billMasterList = JsonConvert.DeserializeObject<BillMaster>(json);
}
And the model classes the deserializer is binding to:
public class BillMaster
{
public string Status { get; set; }
public BillMasterList masterlist { get; set; }
}
public class BillMasterList
{
public BillMasterList_Session session { get; set; }
public Dictionary<int, BillMasterList_Array> BillMasterList_Array { get; set; }
}
public class BillMasterList_Array
{
public int bill_id { get; set; }
public string number { get; set; }
public string change_hash { get; set; }
public string url { get; set; }
public string status_date { get; set; }
public string status { get; set; }
public string last_action_date { get; set; }
public string last_action { get; set; }
public string title { get; set; }
public string description { get; set; }
}
When I run the code I don't throw any errors and I have values in my object except for BillMasterList_Array, that is always null. I'm obviously not doing something right but what it is alludes me.

Categories

Resources