I have a JSON Response from web api as this
{"payload":{"items":{"11204":{"title":"The Ugliest Girl?","item_id":"11204","thumb_url":"http:google.11204.jpg","teaser":"We live in the internet generationher purpose in life to her through this adversity.","language_id":"en","media_id":1,"views":"5","shares":"0"},"11228":{"title":"Depressed","item_id":"11228","thumb_url":"http:google.11228.jpg","teaser":"We all get discouraged at times, especially when things go wrong or other people hurt us. Sometimes we can seem to go through a string of disappointments that compound our sadness till we wonder.","language_id":"en","media_id":5,"views":"35","shares":"2"}}
and many more objects in similar manner
How can i parse this to Dictionary or in any other way? The response varies depending on the request.
u can parse your json into an object like :
var parsed = JObject.Parse(Json);
and to get a specific value :
var value = parsed[key];
Using a service like json2csharp.com you can convert your json to C#. Given the json that you have, you will need to modify the classes slightly. Here are consumable classes
public class Item
{
public string title { get; set; }
public string item_id { get; set; }
public string thumb_url { get; set; }
public string teaser { get; set; }
public string language_id { get; set; }
public int media_id { get; set; }
public string views { get; set; }
public string shares { get; set; }
}
public class Payload
{
public ICollection<Item> Items { get; set; }
}
From there you can use a library like Json.Net to convert from the json to these objects. Normally you would be able to convert directly to your classes, but because of the indexes in the names, this is not possible. So you will have to do some conversion yourself.
public Payload ConvertJson(string json)
{
var payload = new Payload();
var container = JToken.Parse(json) as JContainer;
if(container == null) return payload;
payload.Items = new List<Item>(container.Count);
foreach (var child in container)
{
var childJson = child.FirstOrDefault();
if (childJson == null) continue;
var item = childJson.ToObject<Item>();
if (item.item_id == 0)
{
item.item_id = Convert.ToInt32(((JProperty)child).Name);
}
payload.Items.Add(item);
}
return payload;
}
Related
I'm trying and failing to write a program that will make an API call and then turn the returned items into objects that fit my model. Specifically I can't make it deserealize, and I suspect it has something to do with how the json is return compared to what my model looks like.
The data I'm trying to get looks like this;
https://api.nasa.gov/planetary/apod?start_date=2022-03-01&end_date=2022-03-08&api_key=DEMO_KEY
As you can see, it consists of an array of items, but there is no name for the array items. When I paste this into the Get-model with Paste JSON as Classes, I get this;
public class GetApodItemsResult
{
public Class1[] Property1 { get; set; }
}
public class Class1
{
public string copyright { get; set; }
public string date { get; set; }
public string explanation { get; set; }
public string hdurl { get; set; }
public string media_type { get; set; }
public string service_version { get; set; }
public string title { get; set; }
public string url { get; set; }
}
My entire code works just fine up until I need to serialize the JSON with this line:
var responseObject = await response.Content.ReadFromJsonAsync<GetApodItemsResult>();
, where I get this message;
System.Text.Json.JsonException: 'The JSON value could not be converted to UnnamedSpaceProject.Models.GetApodItemsResult.
Interestingly I know that the code works on a spotify api call, so the code really should work largely the same, which leads me to believe that the problem is with how the JSON is formatted.
How do I get around that? Because I don't see a way to have the root object contain an unnamed array.
Your GetApodItemsResult class is not a valid class to deserialize the content you get from server, the correct deserialization type will be List<Class1> or Class1[]
var responseObject = await response.Content.ReadFromJsonAsync<List<Class1>>();
I recommend you to use more meaningful name instead of Class1 you can name it Apod (acronym for Astronomy Picture of the Day)
Full working code:
using System.Text.Json;
using System.Text.Json.Serialization;
HttpClient client = new HttpClient();
const string BaseUrl = #"https://api.nasa.gov/";
var response = await client.GetAsync($"{BaseUrl}planetary/apod?start_date=2022-03-01&end_date=2022-03-08&api_key=DEMO_KEY");
if ((response.StatusCode != System.Net.HttpStatusCode.OK))
{
Console.Error.WriteLine("field to fetch data from server");
}
var responseBody = await response.Content.ReadAsStringAsync();
var pictuersList = JsonSerializer.Deserialize<List<Apod>>(responseBody);
Console.WriteLine($"there is {pictuersList?.Count} apod downloaded successflly");
Console.WriteLine("done");
public class Apod
{
[JsonPropertyName("copyright")]
public string Copyright { get; set; } = "";
[JsonPropertyName("date")]
public string Date { get; set; } = "";
[JsonPropertyName("explanation")]
public string Explanation { get; set; } = "";
[JsonPropertyName("hdurl")]
public string Hdurl { get; set; } = "";
[JsonPropertyName("media_type")]
public string MediaType { get; set; } = "";
[JsonPropertyName("service_version")]
public string ServiceVersion { get; set; } = "";
[JsonPropertyName("title")]
public string Title { get; set; } = "";
[JsonPropertyName("url")]
public string Url { get; set; } = "";
}
The object your JSON containing is not some container with the array in it, it IS the array. So, the correct code would be like this:
var responseObject = await response.Content.ReadFromJsonAsync<Class1[]>();
The correct JSON for your code would look like this:
{
"Property1": [{
"copyright": "Jeff DaiTWAN",
"date": "2022-03-01",
"url": "https://apod.nasa.gov/apod/image/2203/DuelingBands_Dai_960.jpg"
}]
}
I am getting tdata from a certain endpoint and the problem id on serialization to my classes. I want to cast the bellow data to my class but cant get how the class should be structured. Check out the data .....
{
"-LYG_AI_oGYjNBrzMlKF": {
"chatDispayText": "",
"chatId": "-LYG_AI_oGYjNBrzMlKF",
"chatName": "",
"chattype": "single",
"imageUrl": "https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Amazing-celebrities-hd-wallpaper-PIC-WPD004734.jpg",
"lastMessageSent": "aiye",
"lastMessageSentTime": 1549704416263,
"synched": false,
"users": {
"-LYG_AIZ5MvTbjR7DACe": "Uicpm3L15TX0c15pKCI6KUEARyB3",
"-LYG_AI_oGYjNBrzMlKE": "Xsr0z9lsqNOEytX61lJvaGz1A8F2"
}
}
}
If the data you get out the endpoint has a dynamic structure, you can make use of a key-vale pair collection or a dictionary. For instance:
JObject jObject = JObject.Parse(Data); // This would already give you a key-value pair collection
Dictionary<String,Object> collection = new Dictionary<String, Object>();
foreach(var obj in jObject){
collection.Add(obj.Key, obj.Value);
}
However, this isn't a strongly typed approach which means that it is not effective in the majority of scenarios. A better solution when dealing with endpoints would be to define a class with fixed schema, actually something you need in your code, and then map the class to the object yielded by the endpoint using a metadata struct. For example:
public class ChatInfoModel
{
[JsonProperty(Metadata.ChatId)]
public long ChatId { get; set; }
[JsonProperty(Metadata.ChatId, Required = Required.AllowNull)]
public String Message { get; set; }
}
public struct Metadata
{
public const String ChatId = "userChatId";
public const String Message = "messageTxt";
}
And then
var deserializedObject = JsonConvert.DeserializeObject<ChatInfoModel>(data);
However, if your class has the exact same naming convention (but should not necessarily follow the camelCase naming convention) for its properties as in the serialized data, the JsonProperty attribute would not be needed.
You can also deserialize the object without using JsonProperty attribute manually using the first approach, and it is actually advantageous in certain scenarios where your schema comes from a configuration file rather than a struct.
Take inspiration from the Structure below:
public class Rootobject
{
public LYG_AI_Ogyjnbrzmlkf LYG_AI_oGYjNBrzMlKF { get; set; }
}
public class LYG_AI_Ogyjnbrzmlkf
{
public string chatDispayText { get; set; }
public string chatId { get; set; }
public string chatName { get; set; }
public string chattype { get; set; }
public string imageUrl { get; set; }
public string lastMessageSent { get; set; }
public long lastMessageSentTime { get; set; }
public bool synched { get; set; }
public Users users { get; set; }
}
public class Users
{
public string LYG_AIZ5MvTbjR7DACe { get; set; }
public string LYG_AI_oGYjNBrzMlKE { get; set; }
}
I'm getting json string from webapi like this
{"page":1,"total_results":33,"total_pages":2,"results":
[{"vote_count":8017,"id":603,"video":false,"vote_average":7.9,"title":"The Matrix","popularity":7.82272,"poster_path":"\/lZpWprJqbIFpEV5uoHfoK0KCnTW.jpg","original_language":"en","original_title":"The Matrix","genre_ids":[28,878],"backdrop_path":"\/7u3pxc0K1wx32IleAkLv78MKgrw.jpg","adult":false,"overview":"Set in the 22nd century, The Matrix tells the story of a computer hacker who joins a group of underground insurgents fighting the vast and powerful computers who now rule the earth.","release_date":"1999-03-30"},
{"vote_count":2750,"id":605,"video":false,"vote_average":6.4,"title":"The Matrix Revolutions","popularity":5.073697,"poster_path":"\/sKogjhfs5q3azmpW7DFKKAeLEG8.jpg","original_language":"en","original_title":"The Matrix Revolutions","genre_ids":[12,28,53,878],"backdrop_path":"\/pdVHUsb2eEz9ALNTr6wfRJe5xVa.jpg","adult":false,"overview":"The human city of Zion defends itself against the massive invasion of the machines as Neo fights to end the war at another front while also opposing the rogue Agent Smith.","release_date":"2003-11-05"},
{"vote_count":0,"id":411948,"video":false,"vote_average":0,"title":"Matrix","popularity":1.004394,"poster_path":"\/cseRq8R9RGN66SNUgcD7RJAxBI7.jpg","original_language":"en","original_title":"Matrix","genre_ids":[],"backdrop_path":null,"adult":false,"overview":"John Whitney, Sr. (April 8, 1917 – September 22, 1995) was an American animator, composer and inventor, widely considered to be one of the fathers of computer animation.","release_date":"1971-05-18"}]}
I only want to get title from above string into list.
Here's my code
public List<string> ExtractMoviesList(string movieTitle)
{
using (var client = new HttpClient())
{
// HTTP GET
var response = client.GetAsync(string.Format("{0}{1}", movies_Url, movieTitle)).Result;
using (HttpContent content = response.Content)
{
var json = content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<List<Movies>>(json.Result);
return result.Select(p=>p.Title).ToList();
}
}
}
There's something wrong with this line of code: var result = JsonConvert.DeserializeObject<List<Movies>>(json.Result); after this line executed the var result is getting just null.
Your problem is that you are trying to deserialize your JSON as a List<T>, but the root object in your JSON is not an array, it's an object. This is easy to see if you format and indent your JSON using, say, https://jsonformatter.curiousconcept.com/:
{
"page":1,
"total_results":33,
"total_pages":2,
"results":[
{
"title":"The Matrix",
// Other fields
},
// Other movies
]
}
The data model to which you are binding your JSON must reflect this outer container object for deserialization to succeed. Luckily http://json2csharp.com/ or Paste JSON as Classes will generate one for you:
public class Movie
{
public string title { get; set; }
public int vote_count { get; set; }
public int id { get; set; }
public bool video { get; set; }
public double vote_average { get; set; }
public double popularity { get; set; }
public string poster_path { get; set; }
public string original_language { get; set; }
public string original_title { get; set; }
public List<object> genre_ids { get; set; }
public string backdrop_path { get; set; }
public bool adult { get; set; }
public string overview { get; set; }
public string release_date { get; set; }
}
public class RootObject
{
public int page { get; set; }
public int total_results { get; set; }
public int total_pages { get; set; }
public List<Movie> results { get; set; }
}
Now you can do:
var result = JsonConvert.DeserializeObject<RootObject>(json.Result);
return result.results.Select(m => m.title).ToList();
Incidentally, if you don't want to create a data model just to extract the titles from this JSON, you can use Json.NET's LINQ to JSON functionality to load and query the JSON directly:
var result = JToken.Parse(json.Result);
return result.SelectTokens("results[*].title").Select(t => (string)t).ToList();
Here I am using SelectTokens() with the JsonPATH wildcard operator [*] to find all entries in the results array.
Working .Net fiddle showing both options.
I want to create an Windows Forms application that uses data from football-data.org. This is my first time I'm working with restful/JSON and I'm stuck.
When I try to get all of the leagues that football-data.org provides this is the response you get from the football-data.org api : response
I use the following code to get all of the data correct:(lbResultBox is a listbox)
private void btAllLeagues_Click(object sender, EventArgs e)
{
lbResultBox.Items.Clear();
List<Leagues> LL = GetLeagues("soccerseasons");
foreach (var item in LL)
{
lbResultBox.Items.Add(item.caption);
}
}
public static List<Leagues> GetLeagues(string endurl)
{
var syncClient = new WebClient();
var content = syncClient.DownloadString(baseurl + endurl);
return JsonConvert.DeserializeObject<List<Leagues>>(content);
}
public class Leagues
{
public IDictionary<string,LeagueLinks> _links { get; set; }
public string caption { get; set; }
public string league { get; set; }
public string year { get; set; }
public string numberOfTeams { get; set; }
public string numberOfGames { get; set; }
public string lastUpdated { get; set; }
}
public class LeagueLinks
{
public string href { get; set; }
}
This works.
But when I try to get all the teams from a league this is the response i get from the api. I use this code:
private void btAllTeams_Click(object sender, EventArgs e)
{
List<LeagueTeams> LT = GetLeagueTeams("soccerseasons/" + id + "/teams");
}
public static List<LeagueTeams> GetLeagueTeams(string endurl)
{
var syncClient = new WebClient();
var content = syncClient.DownloadString(baseurl + endurl);
return JsonConvert.DeserializeObject<List<LeagueTeams>>(content);
}
public class LeagueTeams
{
public IDictionary<string, LeagueLinks> _links { get; set; }
public string count { get; set; }
public IDictionary<string, LeagueTeam> teams { get; set; }
}
public class LeagueTeam
{
public IDictionary<string, TeamLinks> _links { get; set; }
public string name { get; set; }
public string code { get; set; }
public string shortName { get; set; }
public string squadMarketValue { get; set; }
public string crestUrl { get; set; }
}
public class TeamLinks
{
public string href { get; set; }
}
But i get the following error:
An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
Additional information: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type System.Collections.Generic.IDictionary`2[System.String,FootballTestApplication.LeagueTeam]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
When i look at what the api give's me for response I can see a difference in how the response begins. With collecting all of the leagues it starts and ends with brackets ([]) but when collecting the teams in a league it doesn't start or end with brackets. When I Add the brackets myself I still get the error. This is how I add the brackets:
return JsonConvert.DeserializeObject<List<LeagueTeams>>("["+content+"]");
What am I doing wrong here?
The problem lies in the fact that the JSON returned is NOT an Array, List, Dictionary or other Enumerable but is instead an object of it's own.
If we take your JSON from that API link and go through it element by element, we learn that your Type is wrong.
You tell the JSON Serializer that the root object is a List<LeagueTeams>, but it is not. It is in fact a single LeagueTeams object.
If we make that modification to the deserializer, and we make a couple modifications to your LeagueTeams class (mostly the fact that you were deserializing the links property wrong), you are all set:
public class LeagueTeams
{
public List<IDictionary<string, string>> _links { get; set; }
public string count { get; set; }
public List<LeagueTeam> teams { get; set; }
}
return JsonConvert.DeserializeObject<LeagueTeams>(content);
I've attached an image of the results.
Additional Notes
Another thing to note: the JSON to C# website (http://json2csharp.com/) does NOT handle this JSON correctly, it in fact generates an object that is almost correct, but not quite. (Though, the generated object will still work properly, it is not an entirely correct mapping of the input JSON.) This is due, in part, to the fault of the generator, but also the website. It attempts to generate a list of a two-property Link element for the root links item, where it instead should have used a list of a dictionary to get correct mappings. (Though, this is a bit befuddling, it is the most correct approach.) Another thing to keep into consideration, yes, it does fine for getting a good start, but generally speaking, proper JSON handling requires more meticulous inspection of the generated objects to guarantee correctness.
I am sending the following request parameters to my service; among which, is the filter parameter which is a multidimensional array:
filter[0][field]:homeCountry
filter[0][data][type]:string
filter[0][data][value]:united s
page:2
start:200
limit:200
sort:homeCountry
dir:ASC
The querystring is encoded like so:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
Currently, my C# request object looks like this:
public class PaymentRateTripRequest
{
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
}
How can I modify my request object to receive the filter parameter which could be a multidimensional array?
Note: I am using ServiceStack.
The only way I can think is to send the entire request object as a parameter to my method like so:
public object Get(PaymentRateTripRequest req)
{
return _repository.GetAllRates(req.start, req.limit, req.sort, req.dir, this.Request.OriginalRequest);
}
But, this doesn't seem like the best solution.
Edit: this.Request.QueryString
this.Request.QueryString
{filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
[System.Web.HttpValueCollection]: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
base {System.Collections.Specialized.NameObjectCollectionBase}: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
_all: null
_allKeys: {string[8]}
AllKeys: {string[8]}
Edit: filter is still empty.
This is an alternative solution that requires no changes to your client and therefore will accept the query string in the format you have currently:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
The disadvantage of this method is that it's more code to maintain. The JSV method is simpler.
Request attribute to populate the filter from the querystring:
We can use a ServiceStack filter to intercept the query string before it reaches the action method. It can then parse the custom filter format and populate the filter object of the DTO.
public class FilterAttribute : Attribute, IHasRequestFilter
{
IHasRequestFilter IHasRequestFilter.Copy()
{
return this;
}
public int Priority { get { return int.MinValue; } }
FilterField CreateOrUpdateField(ref Dictionary<string, FilterField> filter, string id)
{
if(filter.ContainsKey(id))
return filter[id];
var field = new FilterField { Data = new Dictionary<string, object>() };
filter.Add(id, field);
return field;
}
public void RequestFilter(IRequest req, IResponse res, object requestDto)
{
var filteredDto = requestDto as IFilter;
if(filteredDto == null)
return;
const string fieldPattern = #"filter\[([A-Za-z0-9]+)\]\[field\]";
const string dataPattern = #"filter\[([A-Za-z0-9]+)\]\[data\]\[([A-Za-z0-9]+)\]";
Dictionary<string, FilterField> filter = new Dictionary<string, FilterField>();
foreach(var property in req.QueryString.AllKeys)
{
Match match = Regex.Match(property, fieldPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Field
var id = match.Groups[1].Value;
var field = CreateOrUpdateField(ref filter, id);
field.Field = req.QueryString[property];
} else {
match = Regex.Match(property, dataPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Data value
var id = match.Groups[1].Value;
var keyName = match.Groups[2].Value;
var field = CreateOrUpdateField(ref filter, id);
if(!field.Data.ContainsKey(keyName))
field.Data.Add(keyName, req.QueryString[property]);
}
}
}
filteredDto.Filter = filter.Values.ToArray();
}
}
You will also need to add this interface and FilterField class:
public class FilterField
{
public string Field { get; set; }
public Dictionary<string,object> Data { get; set; }
}
public interface IFilter
{
FilterField[] filter { get; set; }
}
Then you simply need to update your DTO so it looks like this:
[Route("/paymentratetrip", "GET"]
[Filter]
public class PaymentRateTripRequest : IFilter
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
You should add a property with the filter to your DTO, such as below:
public class PaymentRateTripRequest
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
public class FilterField
{
public string field { get; set; }
public Dictionary<string,object> data { get; set; }
}
This will allow you to add any number of fields to filter by, and by making the data property of the FilterField a Dictionary<string, object> you can add as many data properties as needed.
Then you can populate the filter parameter in your PaymentRateTripRequest using JSV format. You can learn about JSV format here. JSV Format (i.e. JSON-like Separated Values) is a JSON inspired format that uses CSV-style escaping for the least overhead and optimal performance.
paymentratetrip.json?filter=[{field:homeCountry,data:{type:string,value:"united s"}},{field:other,data:{type:int,value:34,special:true}}]&page=2&start=200&limit=200&sort=homeCountry&dir=ASC
Then you can access the filter as a regular property on your request.
Hope this helps.