JSON.NET Resolving Nested Data Types - c#

I'm new to JSON.NET, and I've been playing with the new Marvel API that was recently released.
When I call this API it will return the following JSON Data Structure:-
{
"code": 200,
"status": "Ok",
"etag": "f0fbae65eb2f8f28bdeea0a29be8749a4e67acb3",
"data":
{
"offset": 0,
"limit": 20,
"total": 30920,
"count": 20,
"results": [{array of objects}}]
}
}
I can create Classes for this Data like this :
public class Rootobject
{
public int code { get; set; }
public string status { get; set; }
public string etag { get; set; }
public Data data { get; set; }
}
public class Data
{
public int offset { get; set; }
public int limit { get; set; }
public int total { get; set; }
public int count { get; set; }
public Result[] results { get; set; }
}
public class Result
{
}
Now, my issue. The Results that come back from the API can relate to different Objects, it could be results relating to Characters, Comics, Series etc. The objects all hold different properties.
I need to be able to swap out the Result Class properties based on the Entity Type that the results relate too?
Can this actually be done?

You can use var jObj = JObject.Parse(jsonString) then discover what object type it is by which properties are available on the object.
jObj["someComicSpecificProperty"] != null
However this is not full proof and will need to be done on a per object basis for the results array.
An alternate approach I have seen people use is to have a property on the object that is "typeName".
However the root cause of this problem is that you are trying to strongly type a property that is not strongly typed. I would really recommend splitting these different types of results out into different properties so that you don't have this problem.

As promised, I've posted the anser to this problem. It turns out that the JSON response has nested data covering all related data-types, very much like a relational database.
I found something really cool, I basically made a request to the API and converted its response to a string. I then used the debugger to take a copy of the contents to the clipboard.
I created a new Class and Called it MarvelResponse.
I added the NewtonSoft.Json directive to the file, and used the Paste Special option from Edit Menu in VS2012. Here you can paste the option "Paste as JSON CLasses".
After some minor tweaking here is what it provided :-
namespace Kaiser.Training.Data.JSONClasses
{
public class MarvelResponse
{
public int code { get; set; }
public string status { get; set; }
public string etag { get; set; }
public Data data { get; set; }
}
public class Data
{
public int offset { get; set; }
public int limit { get; set; }
public int total { get; set; }
public int count { get; set; }
public Result[] results { get; set; }
}
public class Result
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public DateTime modified { get; set; }
public Thumbnail thumbnail { get; set; }
public string resourceURI { get; set; }
public Comics comics { get; set; }
public Series series { get; set; }
public Stories stories { get; set; }
public Events events { get; set; }
public Url[] urls { get; set; }
}
public class Thumbnail
{
public string path { get; set; }
public string extension { get; set; }
}
public class Comics
{
public int available { get; set; }
public string collectionURI { get; set; }
public ComicResourceUriItem[] items { get; set; }
public int returned { get; set; }
}
public class ComicResourceUriItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Series
{
public int available { get; set; }
public string collectionURI { get; set; }
public SeriesResourceItem[] items { get; set; }
public int returned { get; set; }
}
public class SeriesResourceItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Stories
{
public int available { get; set; }
public string collectionURI { get; set; }
public StoriesResourceItem[] items { get; set; }
public int returned { get; set; }
}
public class StoriesResourceItem
{
public string resourceURI { get; set; }
public string name { get; set; }
public string type { get; set; }
}
public class Events
{
public int available { get; set; }
public string collectionURI { get; set; }
public EventsResourceUriItem[] items { get; set; }
public int returned { get; set; }
}
public class EventsResourceUriItem
{
public string resourceURI { get; set; }
public string name { get; set; }
}
public class Url
{
public string type { get; set; }
public string url { get; set; }
}
}
This was a huge help! Hope someone else finds it useful.

Related

How to process a json response from an API in C#

I've read and reread articles online on how to do this, but it's probably something simple. I'm trying to learn how to process a json response from an API call. I have a simple method I call from Main()
public async void apiTestCall()
{
var httpCall = new HttpClient();
var uri = new Uri("https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key=DEMO_KEY");
var result = new DataModel();
var response = await httpCall.GetStringAsync(uri);
result = JsonSerializer.Deserialize<DataModel>(response);
I am expecting "result" to be a DataModel object with the data populated. Right now, there is nothing.
Here is the DataModel class
using System.Collections.Generic;
namespace APITestProject
{
class DataModel
{
public class Camera
{
public int id { get; set; }
public string name { get; set; }
public int rover_id { get; set; }
public string full_name { get; set; }
}
public class Rover
{
public int id { get; set; }
public string name { get; set; }
public string landing_date { get; set; }
public string launch_date { get; set; }
public string status { get; set; }
}
public class Photo
{
public int id { get; set; }
public int sol { get; set; }
public Camera camera { get; set; }
public string img_src { get; set; }
public string earth_date { get; set; }
public Rover rover { get; set; }
}
public class Example
{
public IList<Photo> photos { get; set; }
}
}
}
Any suggestions would be greatly appreciated.
Edit #1: Here is the first 3 entries in the json. I didn't want to post the whole thing but the URL is valid for anyone to run and see the response.
{"photos":[{"id":424926,"sol":1000,"camera":{"id":22,"name":"MAST","rover_id":5,"full_name":"Mast Camera"},"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631200305217E01_DXXX.jpg","earth_date":"2015-05-30","rover":{"id":5,"name":"Curiosity","landing_date":"2012-08-06","launch_date":"2011-11-26","status":"active"}},{"id":424927,"sol":1000,"camera":{"id":22,"name":"MAST","rover_id":5,"full_name":"Mast Camera"},"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631190503679E04_DXXX.jpg","earth_date":"2015-05-30","rover":
Edit #2: I made some changes based on the comments so far and I used the Paste > Special > JSON as Classes and removed the "wrapper" class. Now I get the populated object. FYI, here is the new class VS generated:
namespace APITestProject
{
public class DataModel
{
public Photo[] photos { get; set; }
}
public class Photo
{
public int id { get; set; }
public int sol { get; set; }
public Camera camera { get; set; }
public string img_src { get; set; }
public string earth_date { get; set; }
public Rover rover { get; set; }
}
public class Camera
{
public int id { get; set; }
public string name { get; set; }
public int rover_id { get; set; }
public string full_name { get; set; }
}
public class Rover
{
public int id { get; set; }
public string name { get; set; }
public string landing_date { get; set; }
public string launch_date { get; set; }
public string status { get; set; }
}
}
Can you try the following amendment
var result = JsonSerializer.Deserialize<DataModel.Example>(response);
as it looks like DataModel.Example is actually the class that you are trying to deserialize to based on the response that comes from the following call -
https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key=DEMO_KEY
i.e. It returns an object containing an array of photo objects as you have defined them. As someone else mentioned, no need to encapsulate all of these classes within another class.
You class DataModel just defines other classes, but do not instance them.
According to the data inside the response, you DataModel class should have at least Photos member. It could be type of List<Photo>
class DataModel {
...
public List<Photo> Photos { get; set; }
}
The Photos definition says, that there is some list of photos of Photo type to be expected.

C# MVC can't Deserialize a tuple

I have a Json model that looks like this
private class SearchMetadataJson
{
public string entertain { get; set; }
public string master { get; set; }
public string memail { get; set; }
public string key { get; set; }
public (int, string)[] mood { get; set; }
public int? soundnumber { get; set; }
public int? ftv { get; set; }
public int? com { get; set; }
public (int, string)[] sims { get; set; }
public (int, string)[] keysecond { get; set; }
public string popt { get; set; }
public (string, string) syncs { get; set; }
}
And I try to de-serialize the object like this
var CommentObj = JsonSerializer.Deserialize<SearchMetadataJson>(CommentAsString);
The data that I'm trying to de-serialize (aka "CommentAsString") looks like this
"{\"entertain\":\"PEG\",\"master\":\"Phos Ent Group\",\"memail\":\"example#example.com\",\"key\":\"Db\",\"mood\":{\"1\":\"TypeA\",\"4\":\"TypeB\",\"5\":\"TypeC\"},\"soundnumber\":\"5\",\"ftv\":\"4\",\"com\":\"3\",\"sims\":{\"1\":\"Band1\",\"2\":\"Band2\"},\"keysecond\":{\"1\":\"KeyWord1\",\"2\":\"KeyWord2\",\"3\":\"KeyWord3\"},\"syncs\":{\"Other pubber\":\"example2#example.com\"}}"
But I keep getting this error
Does anyone see what the problem is?
Update
The integers in CommentAsString are variables and will be different every time the function is called so I can't make a Json Object that has a key value of a particular integer.
Let's look at the actual formatted data structure
{
"entertain":"PEG",
"master":"Phos Ent Group",
"memail":"example#example.com",
"key":"Db",
"mood":{
"1":"TypeA",
"4":"TypeB",
"5":"TypeC"
},
"soundnumber":"5",
"ftv":"4",
"com":"3",
"sims":{
"1":"Band1",
"2":"Band2"
},
"keysecond":{
"1":"KeyWord1",
"2":"KeyWord2",
"3":"KeyWord3"
},
"syncs":{
"Other pubber":"example2#example.com"
}
}
Converting these to an array of tuple would be unusual. What you seemingly have are dictionary's
Example
private class SearchMetadataJson
{
public string entertain { get; set; }
public string master { get; set; }
public string memail { get; set; }
public string key { get; set; }
public Dictionary<int,string> mood { get; set; }
public int? soundnumber { get; set; }
public int? ftv { get; set; }
public int? com { get; set; }
public Dictionary<int,string> sims { get; set; }
public Dictionary<int,string> keysecond { get; set; }
public string popt { get; set; }
// public (string, string) syncs { get; set; }
}
It's debatable whether the last property is an object or another dictionary as well.
"syncs":{
"Other pubber":"example2#example.com"
}
However, I'll leave that up to you.
your have error in the model, use this site for convert your json in c#
https://json2csharp.com/
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Mood {
public string _1 { get; set; }
public string _4 { get; set; }
public string _5 { get; set; }
}
public class Sims {
public string _1 { get; set; }
public string _2 { get; set; }
}
public class Keysecond {
public string _1 { get; set; }
public string _2 { get; set; }
public string _3 { get; set; }
}
public class Syncs {
public string Otherpubber { get; set; }
}
public class Root {
public string entertain { get; set; }
public string master { get; set; }
public string memail { get; set; }
public string key { get; set; }
public Mood mood { get; set; }
public string soundnumber { get; set; }
public string ftv { get; set; }
public string com { get; set; }
public Sims sims { get; set; }
public Keysecond keysecond { get; set; }
public Syncs syncs { get; set; }
}
try again with using this
first read your string
this is the response using post, get or delete
var response = await client.PostAsync("your-url", datasBody);
var contentData = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
var CommentObj = JsonSerializer.Deserialize<Root>(contentData, options);
if your model is bad or not match using
[JsonProperty("entertain")]
public string entertain { get; set; }
You will either need to use a custom converter or convert your tuples into separate classes with fields to explain what each field is used for.

Creating a C# class from convoluted JSON object where dynamic variable would be class name [duplicate]

This question already has answers here:
How can I parse a JSON string that would cause illegal C# identifiers?
(3 answers)
Closed 2 years ago.
I'm trying to query the most important information from wikipedia articles using the wikimedia API. Within my code I have the following line:
WikiArticleModel article = await response.Content.ReadAsAsync<WikiArticleModel>().ConfigureAwait(false);
This is a example of the way my JSON object looks like when testing on the article from the planet Jupiter:
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "jupiter",
"to": "Jupiter"
}
],
"pages": {
"38930": {
"pageid": 38930,
"ns": 0,
"title": "Jupiter",
"extract": ">>> Her comes the first section of the article, which I deleted to make
this shorter <<<",
"description": "Fifth planet from the Sun and largest planet in the Solar System",
"descriptionsource": "local",
"original": {
"source": "https://upload.wikimedia.org/wikipedia/commons/2/2b/Jupiter_and_its_shrunken_Great_Red_Spot.jpg",
"width": 940,
"height": 940
}
}
}
}
}
The question is now, how should my WikiArticleModel class look like? Using the build-in VS Studio too "Paste JSON as class" I get the following result:
public class WikiArticleModel
{
public string batchcomplete { get; set; }
public Query query { get; set; }
}
public class Query
{
public Normalized[] normalized { get; set; }
public Pages pages { get; set; }
}
public class Pages
{
public _38930 _38930 { get; set; }
}
public class _38930
{
public int pageid { get; set; }
public int ns { get; set; }
public string title { get; set; }
public string extract { get; set; }
public string description { get; set; }
public string descriptionsource { get; set; }
public Original original { get; set; }
}
public class Original
{
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
}
public class Normalized
{
public string from { get; set; }
public string to { get; set; }
}
Which is OK and what I would expect, except for the class _38930, which is just the pageid and would change with every query.
What is the correct way to deserialize this object? Or is it a better approach to just get a object as response and fill the model class manually in this case?
Additionally, I actually only need certain parameters from the JSON object (e.g. title, extract, description,..) - is there a way to get these directly into a model class containing only the properties I need?
This is the way to do it natively, Pages is actually a Dictionary<int, Page>.
public class WikiArticleModel
{
public string batchcomplete { get; set; }
public Query query { get; set; }
}
public class Query
{
public List<Normalized> normalized { get; set; }
public Pages pages { get; set; }
}
[JsonDictionary]
public class Pages : Dictionary<int, Page> { }
public class Page
{
public int pageid { get; set; }
public int ns { get; set; }
public string title { get; set; }
public string extract { get; set; }
public string description { get; set; }
public string descriptionsource { get; set; }
public Original original { get; set; }
}
public class Original
{
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
}
public class Normalized
{
public string from { get; set; }
public string to { get; set; }
}
I would recommend using JObject.Parse from Newtonsoft.Json.Linq and parsing it based on the name of the keys that page has. Something like this,
public class Page
{
public int pageid { get; set; }
public int ns { get; set; }
public string title { get; set; }
public string extract { get; set; }
public string description { get; set; }
public string descriptionsource { get; set; }
public Original original { get; set; }
}
public class Original
{
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
}
public class Normalized
{
public string from { get; set; }
public string to { get; set; }
}
// you can deserialize like this,
var jobj = JObject.Parse(json);
var props = ((JObject)jobj["query"]["pages"]).Properties();
Page page = JsonConvert.DeserializeObject<Page>(jobj["query"]["pages"][props.First().Name].ToString());
You can use a foreach loop on each of the properties of pages and iterate through those as well (instead of using props.First().

Using JavaScriptSerializer to Deserialize JSON C# - type not supported for deserialization of an array

New to C# and JSON but basically I have to use a C# script component in SSIS to extract data from a web service. I was able to do it initially with a different JSON format but this one includes an array.
I get the following error:
"Type 'Rootobject' is not supported for deserialization of an array."
Below is how I call the Deserializer:
//Deserialize our JSON
JavaScriptSerializer sr = new
jsonResponse = sr.Deserialize<Rootobject>(responseFromServer);
Here is my JSON structure (not quite all since fairly large):
[
{
"status":"SUCCESS",
"object":{
"responseStatusCode":0,
"productId":"35100003",
"cansimId":"251-0008",
"cubeTitleEn":"Average counts of young persons in provincial and territorial correctional services",
"cubeTitleFr":"Comptes moyens des adolescents dans les services correctionnels provinciaux et territoriaux",
"cubeStartDate":"1997-01-01",
"cubeEndDate":"2017-01-01",
"frequencyCode":12,
"nbSeriesCube":174,
"nbDatapointsCube":3468,
"releaseTime":"2019-05-09T08:30",
"archiveStatusCode":"2",
"archiveStatusEn":"CURRENT - a cube available to the public and that is current",
"archiveStatusFr":"ACTIF - un cube qui est disponible au public et qui est toujours mise a jour",
"subjectCode":[
"350102",
"4211"
],
"surveyCode":[
"3313"
],
"dimension":[ ],
"footnote":[ ],
"correction":[
]
}
}
]
Finally here is my Class structure obtained through Paste Special in Visual Studio:
public class Rootobject
{
public Class1[] Property1 { get; set; }
}
public class Class1
{
public string status { get; set; }
public Object _object { get; set; }
}
public class Object
{
public int responseStatusCode { get; set; }
public string productId { get; set; }
public string cansimId { get; set; }
public string cubeTitleEn { get; set; }
public string cubeTitleFr { get; set; }
public string cubeStartDate { get; set; }
public string cubeEndDate { get; set; }
public int frequencyCode { get; set; }
public int nbSeriesCube { get; set; }
public int nbDatapointsCube { get; set; }
public string releaseTime { get; set; }
public string archiveStatusCode { get; set; }
public string archiveStatusEn { get; set; }
public string archiveStatusFr { get; set; }
public string[] subjectCode { get; set; }
public string[] surveyCode { get; set; }
public Dimension[] dimension { get; set; }
public Footnote[] footnote { get; set; }
public object[] correction { get; set; }
}
public class Dimension
{
public int dimensionPositionId { get; set; }
public string dimensionNameEn { get; set; }
public string dimensionNameFr { get; set; }
public bool hasUom { get; set; }
public Member[] member { get; set; }
}
public class Member
{
public int memberId { get; set; }
public int? parentMemberId { get; set; }
public string memberNameEn { get; set; }
public string memberNameFr { get; set; }
public string classificationCode { get; set; }
public string classificationTypeCode { get; set; }
public int? geoLevel { get; set; }
public int? vintage { get; set; }
public int terminated { get; set; }
public int? memberUomCode { get; set; }
}
public class Footnote
{
public int footnoteId { get; set; }
public string footnotesEn { get; set; }
public string footnotesFr { get; set; }
public Link link { get; set; }
}
public class Link
{
public int footnoteId { get; set; }
public int dimensionPositionId { get; set; }
public int memberId { get; set; }
}
I know the problem lies within how I call the Deserialize<Rootobject> and different data types but I wasn't able to find the solution. Any suggestions are appreciated.
AV
Try deserializing directly to a list of Class1.
jsonResponse = sr.Deserialize<List<Class1>>(responseFromServer);
Also, don't use Object as your class name. That's a really bad practice.
You can control how JSON.NET serializes/deserializes a property as shown here: How can I change property names when serializing with Json.net?
e.g.
[JsonProperty(PropertyName = "object")]
public class MyObject
{
}

Deserializing a nested JSON string, cannot access properties

I am having issues deserializing a nested JSON array from the Genius lyric website API. I formulated the object using http://json2csharp.com. When I deserialize the object, I am unable to access the properties inside of the class, which wasn't entirely unexpected, I am just not sure how to properly design an actual solution to the problem. The JSON object conversions work fine when they are not nested.
What would be the best way to go about handling this?
Here is the conversion code:
string test = await G.SearchGeniusASync(textBox1.Text);
var data = JsonConvert.DeserializeObject<GeniusApiObject>(test);
Here is my class:
class GeniusApiObject
{
public class Meta
{
public int status { get; set; }
}
public class Stats
{
public bool hot { get; set; }
public int unreviewed_annotations { get; set; }
public int concurrents { get; set; }
public int pageviews { get; set; }
}
public class PrimaryArtist
{
public string api_path { get; set; }
public string header_image_url { get; set; }
public int id { get; set; }
public string image_url { get; set; }
public bool is_meme_verified { get; set; }
public bool is_verified { get; set; }
public string name { get; set; }
public string url { get; set; }
public int iq { get; set; }
}
public class Result
{
public int annotation_count { get; set; }
public string api_path { get; set; }
public string full_title { get; set; }
public string header_image_thumbnail_url { get; set; }
public string header_image_url { get; set; }
public int id { get; set; }
public int lyrics_owner_id { get; set; }
public string lyrics_state { get; set; }
public string path { get; set; }
public int? pyongs_count { get; set; }
public string song_art_image_thumbnail_url { get; set; }
public Stats stats { get; set; }
public string title { get; set; }
public string title_with_featured { get; set; }
public string url { get; set; }
public PrimaryArtist primary_artist { get; set; }
}
public class Hit
{
public List<object> highlights { get; set; }
public string index { get; set; }
public string type { get; set; }
public Result result { get; set; }
}
public class Response
{
public List<Hit> hits { get; set; }
}
public class RootObject
{
public Meta meta { get; set; }
public Response response { get; set; }
}
}
This is the source for the SearchGeniusASync method in case it is helpful:
public async Task<string>SearchGeniusASync(string searchParameter)
{
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", clientAccessToken);
var result = await httpClient.GetAsync(new Uri("https://api.genius.com/search?q=" + searchParameter), HttpCompletionOption.ResponseContentRead);
var data = await result.Content.ReadAsStringAsync();
return data;
}
This is the scope I am given access to:
https://i.imgur.com/9mZMvfp.png
Here's a sample JSON request in plaintext:
https://pastebin.com/iA8dQafW
GeniusApiObject is not needed in the code, but I'll leave it in just because it helps organize things (may be that something else also has a RootObject from the auto-generator).
The problem is that you are trying to deserialize to what is just an empty class, the class itself has no properties, so you can't deserialize to it. You need to deserialize to the GeniusApiObject.RootObject.
var data = JsonConvert.DeserializeObject<GeniusApiObject.RootObject>(test);
Will deserialize to the .RootObject subclass. This is verified working:
Where I'm using File.ReadAllText("test.json") to load the example API data provided.
Here is a .NET Fiddle showing it working (without the root object and only one song in the response). Thanks to #maccttura.

Categories

Resources