I get Match data from a server with json format. in this json, There are two properties (key) whose names are variable and dynamic and depending on the fixture.
for example, I get Match data for fixture_id 256
{
"api": {
"results": 1,
"fixtures": [
{
"fixture_id": 256,
"league_id": 21,
"homeTeam": {
"team_id": 12,
"team_name": "Tottenham"
},
"awayTeam": {
"team_id": 13,
"team_name": "Everton"
},
"lineups": {
"Tottenham": {
"coach": "qqqq",
"formation": "4-2-3-1"
},
"Everton": {
"coach": "rrrr",
"formation": "4-2-3-1"
}
}
}
]
}
}
the class for this json
public class rootMatch
{
[JsonProperty("api")]
public Api Api { get; set; }
}
public class Api
{
[JsonProperty("results")]
public long Results { get; set; }
[JsonProperty("fixtures")]
public List<Fixture> Fixtures { get; set; }
}
public partial class Fixture
{
[JsonProperty("fixture_id")]
public long FixtureId { get; set; }
[JsonProperty("league_id")]
public long LeagueId { get; set; }
[JsonProperty("homeTeam")]
public Team HomeTeam { get; set; }
[JsonProperty("awayTeam")]
public Team AwayTeam { get; set; }
[JsonProperty("lineups")]
public Lineups Lineups { get; set; }
}
public class Lineups
{
[JsonProperty("Tottenham")]
public Team Tottenham{ get; set; }
[JsonProperty("Everton")]
public Team Everton{ get; set; }
}
public class Team
{
[JsonProperty("coach")]
public string Coach { get; set; }
[JsonProperty("formation")]
public string Formation { get; set; }
}
But, the rootMatch class work only, for this json.
Is there a way to change name of Data property during deserialize, change it to static name?
First TeamName at lineups -> change to the HomeTeam
and
Second TeamName at lineups -> change to the AwayTeam
for example, in this json "lineups"
(Tottenham convert -> HomeTeam)
and
(Everton convert -> AwayTeam)
You can add a property
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; set; }
This will contain all properties that couldn't be match. Then you could iterate over the dictionary and process this data as you like.
The "problematic" JSON is the object with two properties where the property names are the team names. These are not known in advance. To handle this you can change the type to a dictionary with a string key.
To create meaningful names I adjusted your types a bit:
This is team:
public class Team
{
[JsonProperty("team_id")]
public int TeamId { get; set; }
[JsonProperty("team_name")]
public string TeamName { get; set; }
}
and this is a lineup:
public class Lineup
{
[JsonProperty("coach")]
public string Coach { get; set; }
[JsonProperty("formation")]
public string Formation { get; set; }
}
The fixture then becomes:
public partial class Fixture
{
[JsonProperty("fixture_id")]
public long FixtureId { get; set; }
[JsonProperty("league_id")]
public long LeagueId { get; set; }
[JsonProperty("homeTeam")]
public Team HomeTeam { get; set; }
[JsonProperty("awayTeam")]
public Team AwayTeam { get; set; }
[JsonProperty("lineups")]
public Dictionary<string, Lineup> Lineups { get; set; }
}
Notice how the lineups JSON property is mapped to a Dictionary<string, Lineup> in C#.
Here is how you can get the lineups for the home and away teams:
var root = JsonConvert.DeserializeObject<rootMatch>(json);
var fixture = root.Api.Fixtures.First();
var homeTeamLineup = fixture.Lineups[fixture.HomeTeam.TeamName];
var awayTeamLineup = fixture.Lineups[fixture.AwayTeam.TeamName];
The team names are used as keys in the dictionary.
I use First to pick the first fixture. You would probably use LINQ or a loop to process all the fixtures.
Related
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");
}
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(),
});
I have the following structure in my framework. I have a helper where I have the following Attribute code, Resolver code and the Serialization method. Link to this in the other part of the question How to create one model class to use with multiple API methods that require different JSON payloads?
I now have any API that takes a Payload that is large with root object. How can I use the SerializeForApiMethod method for this, since it has the root object. I am trying to set the values in the same model object reference for this Jsonpayload.
//Here is the Json schema
{
"tagNode": {
"query": null,
"type": 0,
"filter": null,
"ldapPaths": null,
"editPermissions": 0,
"id": 0,
"disallowed": false,
"name": "NewTag"
},
"parentId": 0
}
//Model class
public class TagModel
{
public static TagModel mode = new TagModel
{
endpointIds = new List<int> { -2147483612, -2147483611 },
tagIds = new List<int> { 35, 37 },
id = -2147483639,
parentId = 37,
nodeId = 1,
oldParentId = null,
isEndpoint = false,
state = 2,
destinationTag = 2,
};
//This Object I am Confused on how to Serialize as above schema it in the
//same class whichI w ant to set the values inside the TagModel instance.
//Also has same attributes like id and parentId used already in the
//other methods and also this complex payload has a class TagNode which is
//the root object.
[UseWithApiMethods("UpdateTag")]
public TagNode tagNode { get; set; }
public object query { get; set; }
public int type { get; set; }
public object filter { get; set; }
public object ldapPaths { get; set; }
public int editPermissions { get; set; }
public int id { get; set; }
public bool disallowed { get; set; }
public string name { get; set; }
public int parentId { get; set; }
//For this Object I am able to serialize it
[UseWithApiMethods("UpdateTagToRoot")]
public int nodeId { get; set; }
[UseWithApiMethods("UpdateTagToRoot")]
public object oldParentId { get; set; }
[UseWithApiMethods("UpdateTagToRoot")]
public bool isEndpoint { get; set; }
[UseWithApiMethods("UpdateTagToRoot")]
public int state { get; set; }
[UseWithApiMethods("UpdateTagToRoot")]
public int destinationTag { get; set; }
//For this Object I am able to serialize it
[UseWithApiMethods("UpdateEndpointsToTags")]
public List<int> endpointIds { get; set; }
[UseWithApiMethods("UpdateEndpointsToTags")]
public List<int> tagIds { get; set; }
//For this Object I am able to serialize it
[UseWithApiMethods("UpdateEndpointsFromTags")]
public int id { get; set; }
[UseWithApiMethods("UpdateEndpointsFromTags")]
public int parentId { get; set; }
}
}
To get the JSON payload you want using the custom resolver from the other question, you would need your class structure to look like this:
public class TagModel
{
[UseWithApiMethods("UpdateTag")]
public TagNode tagNode { get; set; }
public class TagNode
{
[UseWithApiMethods("UpdateTag")]
public object query { get; set; }
[UseWithApiMethods("UpdateTag")]
public int type { get; set; }
[UseWithApiMethods("UpdateTag")]
public object filter { get; set; }
[UseWithApiMethods("UpdateTag")]
public object ldapPaths { get; set; }
[UseWithApiMethods("UpdateTag")]
public int editPermissions { get; set; }
[UseWithApiMethods("UpdateTag")]
public int id { get; set; }
[UseWithApiMethods("UpdateTag")]
public bool disallowed { get; set; }
[UseWithApiMethods("UpdateTag")]
public string name { get; set; }
}
[UseWithApiMethods("UpdateEndpointsFromTags", "UpdateTag")]
public int parentId { get; set; }
... (other properties for your other API methods) ...
}
Note that you must put [UseWithApiMethods("UpdateTag")] on the tagNode property in the root class and also on all the properties within the TagNode child class that you want to include in the JSON. (This is necessary because the resolver will only include properties that you specifically mark with a [UseWithApiMethods] attribute. So you need to mark them all if you want to include them all).
For the parentId property, you were already using it for the UpdateEndpointsFromTags method, but that is no problem; you can also reuse it for other methods like UpdateTag. You just need to add the method names to the [UseWithApiMethods] attribute like this:
[UseWithApiMethods("UpdateEndpointsFromTags", "UpdateTag")]
public int parentId { get; set; }
Here is a fiddle which demonstrates: https://dotnetfiddle.net/6TqbFz
Hopefully this is what you are looking for and resolves your confusion.
When deserializing the following json response I manage to get the subscribers id, e-mail and status, which is easy as these names are always the same and are not dependent on the particular settings in a list. But how can I deserialize the merge-fields and the interests that do have different names in different lists?
{ "members": [ {
"id": "f777bbffab8d1ceca8b757df63c47cb8",
"email_address": "urist.mcvankab+1#freddiesjokes.co",
"unique_email_id": "882e9bec19",
"status": "subscribed",
"merge_fields":{
"FNAME": "",
"LNAME": "" },
"interests":{
"9143cf3bd1": true,
"3a2a927344": false,
"f9c8f5f0ff": false,
"f231b09abc": true,
"bd6e66465f": false },
//And so on...
How can I change my classes to deserialize the response?
My Member Class
public class Member
{
public string id { get; set; }
public string email_address { get; set; }
public string status { get; set; }
//public List<MergeField> mergeFields { get; set; }
//public List<Interests> interests { get; set; }
}
Where the MergeField refers to to my old MergeField class, where the fields were hardcoded
public class MergeField
{
public string FNAME { get; set; }
public string LNAME { get; set; }
public string ADDRESS { get; set; }
public string MERGE4 { get; set; }
}
But I would like to use the same two Mergefield(s) classes that I use to deserialize the json response when requesting a list's merge-fields.
public class MergeFields
{
public List<Mergefield> merge_fields { get; set; }
public string list_id { get; set; }
public int total_items { get; set; }
}
public class Mergefield
{
public string merge_id { get; set; }
public string tag { get; set; }
public string name { get; set; }
public string list_id { get; set; }
}
I found this answer How do deserialize JSON with non-standard (and varying) property names, but I cannot figure out how to use it in my scenarĂo.
You can use Dictionary<string, object> type for the merge fields and Dictionary<string, bool> type for the interests. Here's what your Member class should look like
public class Member
{
public string id { get; set; }
public string email_address { get; set; }
public string status { get; set; }
public Dictionary<string, object> merge_fields { get; set; }
public Dictionary<string, bool> interests { get; set; }
}
Assuming you're using MemberResponse class for the response like below
public class MemberResponse
{
public List<Member> members { get; set; }
public string list_id { get; set; }
public int total_items { get; set; }
}
Here's how you get the value for each merge field and interest
MemberResponse memberResponse = .... // your code that calls the API
// enumerate members
foreach (var member in memberResponse.members)
{
// enumerate merge fields
foreach (var key in member.merge_fields.Keys)
{
// when key = "FNAME", value would be the first name of the member
// when key = "LNAME", value would be the last name of the member
string value = member.merge_fields[key].ToString();
}
// enumerate interests
foreach (var key in member.interests.Keys)
{
// when key = "9143cf3bd1", value would be true
// when key = "3a2a927344", value would be false
// ... and so on
bool value = member.interests[key];
}
}
Unfortunately there's no way to get the name and merge_id of the merge fields using the /lists/{list_id}/members endpoint since the response body only contains the tag of the merge fields, i.e FNAME, LNAME, etc. You'll need to make a separate call to /lists/{list_id}/merge-fields endpoint and compare the tag of each merge field in order to get the related name and merge_id.
The TrackIt Web API (I can't change it) gives me this JSON inside a return object:
...
Notes: {
Note1: {
CreatedBy: "smarteam2"
...
},
Note2: {
CreatedBy: "smarteam2"
CreatedDate: "1/27/2014 2:37:36 PM"
...
}, ...
Where there are N number of notes. I can't figure out how to deserialize this using JSON.Net unless I do something that feels wrong like:
public class TrackItWorkOrderNotes
{
public TrackItWorkOrderNotes Note1 { get; set; }
public TrackItWorkOrderNotes Note2 { get; set; }
public string IsPrivate { get; set; }
public string FullText { get; set; }
public string WorkOrderNoteTypeId { get; set; }
}
And in the parent:
public class TrackitWorkOrder
{
public TrackItWorkOrderNotes Notes { get; set; }
public int Id { get; set; }
...
This "works" by using :
ti = JsonConvert.DeserializeObject<TrackItResponse<TrackitWorkOrder>>(responseString);
I think there must be a better way to get the notes without pre-defining N number of them in the object. I believe TrackIt might have made this difficult by not putting the "Notes" in an array, but is there a smarter way to do this?
Use a Dictionary<string, Note> for the notes:
public class TrackitWorkOrder
{
public Dictionary<string, TrackItWorkOrderNote> Notes { get; set; }
public int Id { get; set; }
...
}
public class TrackItWorkOrderNote
{
public string CreatedBy { get; set; }
public string CreatedDate { get; set; }
...
}