Deserialize Web Api OData response - c#

I have an Entity Framework object returned by OData V4 controller.
I return an IQueryable and if I call the OData endpoint without any OData clause I can succesfully do this:
var content = response.Content.ReadAsAsync<IQueryable<Person>>();
And the response in JSON is the following:
{
"#odata.context":"http://xxx:8082/odata/$metadata#Persons","value":[
{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5",
"Active":true,
"Alert":"Some alerts for the Person",
"Comments":"Some comments for the Person"
}
]
}
But as soon as I start to play with OData, for example by using $expand on a Complex property I get the following exception:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Linq.IQueryable`1[xxx.Person]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
And the response is the following:
{
"#odata.context":"http://aqavnext01:8082/odata/$metadata#Persons","value":[
{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5",
"Active":true,
"Alert":"Some alerts for the Person",
"Comments":"Some comments for the Person",
"Party":{
"Id":"291b9f1c-2587-4a35-993e-00033a81f6d5"
}
}
]
}
And I am deserializing using the same object returned by my Web Api so I don't understand why it fails.
Same issue when I apply $select.

Try deserialize the content like this:
var content = response.Content.ReadAsAsync<ODataResponse<Person>>();
Where ODataResponse is:
internal class ODataResponse<T>
{
public T[] Value { get; set; }
}

If you need access to the #odata.xxx fields in the response JSON (such as implementing a loop for paged results), the following is my implementation which expands on the solution from andygjp.
I'm using RestSharp as my HTTP client and Json.NET for (de)serialization. Steps as follows.
Implement a custom IRestSerializer that uses Json.NET to replace the default RestSharp serializer. Below example implementation:
public class JsonNetSerializer : IRestSerializer
{
private readonly JsonSerializerSettings _settings;
public JsonNetSerializer()
{
_settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
_settings.Converters.Add(new StringEnumConverter());
}
public string Serialize(Parameter parameter) => JsonConvert.SerializeObject(parameter.Value, Formatting.None, _settings);
public string Serialize(object obj) => JsonConvert.SerializeObject(obj, Formatting.None, _settings);
public T Deserialize<T>(IRestResponse response) => JsonConvert.DeserializeObject<T>(response.Content);
public string[] SupportedContentTypes => new string[] { "application/json", "text/json", "text/x-json", "text/javascript", "*+json" };
public DataFormat DataFormat => DataFormat.Json;
public string ContentType { get; set; } = "application/json";
}
Next, define a class that will represent your OData responses. My expanded response class - which includes the #odata.nextLink field - looks as follows.
private class ODataResponse<T>
{
public T[] Value { get; set; }
[JsonProperty("#odata.nextLink")]
public string NextLink { get; set; }
}
Finally, I create an instance of RestClient, setting up the custom serializer previously created:
var client = new RestClient("https://base.url.here/")
.UseSerializer(() => new JsonNetSerializer());
Now when I execute my request, the data in the response object object also contains my OData values.
var response = await client.ExecuteAsync<T>(request);
var nextLink = response.Data.NextLink;
I'm sure this can be done using the standard HttpClient instead of RestSharp, as the real work is done by the serializer. This was just the example implementation I had on hand.

Related

Retuning JObject\dynamic with ServiceStack seems to return

I'm trying to take a JSON string from SQL (works fine with SQL json queries, it's stuctured fine in there) and return it through ServiceStack. There's no errors on deserializing it, I can see the object in debug, but the result from the service is just a bunch of blank nested arrays?
//Tried both of these, same result
this.Json = JObject.Parse(json);
/// or
this.Json = JsonConvert.DeserializeObject<dynamic>(json);
///....
public dynamic Json { get; set; }
/// or
public JObject Json { get; set; }
Can't do a POCO because I don't know the structure, just need to poop back out the json blob.
See Service Return Types and Customize HTTP Responses for different ways to return custom responses in ServiceStack.
If you just want to return the JSON from SQL Server as-is, you can return the json string with the JSON Content Type, e.g:
[AddHeader(ContentType = MimeTypes.Json)]
public string Get(RawJson request)
{
//...
return json;
}
Or use a HttpResult if you need to add additional HTTP Headers:
public string Get(RawJson request)
{
return new HttpResult(json) {
ContentType = MimeTypes.Json,
Headers = {
[HttpHeaders.XXX] = "..."
}
};
}
Either way you should annotate your Request DTO that it returns a string so clients know to return the string response as-is:
public class RawJson : IReturn<string> {}
If you want to return the JSON object as part of a larger payload you can use JS Utils JSON.parse() to parse arbitrary JSON in untyped generic collections, e.g:
public string Get(CustomJson request)
{
return new CustomJsonResponse {
Result = JSON.parse(json)
};
}
Where Result is an object, using object does mean that it wont be supported with Add ServiceStack Reference typed clients and clients would just need to parse it as arbitrary JSON, e.g. JSON.parse(json) in JavaScript.

Deserialize JSON to a dynamic object or a class in C#

I am trying to deserialize JSON into an object so I can add it to elastic search. JSON can be of many different object types in the project so I would like the function to be dynamic.
First I am serializing the Data that I get from EF Core context
var serializedObject = JsonConvert.SerializeObject(document, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
Next I would like to deserialize to an object. For example if I have
public class EValues
{
public dynamic values { get; set; }
}
var test = JsonConvert.DeserializeObject<EValues>(serializedObject.ToString());
I would like the JSON to be deserialized to the below:
{
"values":{
"StudentId":"60712555-ff1d-4a3e-8c81-08d9c2fc4423",
"Student":{
"Name":"string",
"Country":"string",
"Street":"string"
}
}
}
The serializedObject JSON I am actually trying to deserialize:
{
"StudentId":"60712555-ff1d-4a3e-8c81-08d9c2fc4423",
"Student":{
"Name":"string",
"Country":"string",
"Street":"string"
}
}
You can just do:
var test = new EValues {
values = JsonConvert.DeserializeObject<dynamic>(serializedObject)
};
The JSON that would correspond to EValues would have an extra level of nesting { "values" : {} } not present in your serializedObject JSON.

C# .net http | How to get objects layers deep from API

I learned how to get information from an API using the microsoft docs, the microsoft docs don't show how to get nested/layers deep objects. The only video I found that showed how to do it did it something like this. However I can't get it to work, receiving only an error stating this:
"Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Dashboard.Weather' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly."
Any help is appreciated, i'm trying to get the "Weather.description", here's my sample code:
public class Weather
{
public string Description{ get; set; }
}
public class Product
{
public Weather Weather { get; set; }
}
public static class ApiHelper
{
static string city_id = "CITY_ID";
static string api_key = "API_KEY";
public static HttpClient client = new HttpClient();
public static void InitializeClient()
{
client.BaseAddress = new Uri($"http://api.openweathermap.org/data/2.5/weather?id={city_id}&APPID={api_key}");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public static async Task<Weather> GetProductAsync()
{
Product product = null;
HttpResponseMessage response = await client.GetAsync("");
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}
return product.Weather;
}
}
async void SetLabelText()
{
var weather = await ApiHelper.GetProductAsync();
descriptionLabel.Text = $"Description: {weather.Description}";
}
The response from the API is formatted as follows
{"coord":{"lon":-89.59,"lat":41.56},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"base":"stations","main":{"temp":279.27,"feels_like":275.4,"temp_min":278.15,"temp_max":280.37,"pressure":1027,"humidity":74},"visibility":16093,"wind":{"speed":3.1,"deg":200},"clouds":{"all":1},"dt":1576951484,"sys":{"type":1,"id":3561,"country":"US","sunrise":1576934493,"sunset":1576967469},"timezone":-21600,"id":4915397,"name":"Walnut","cod":200}
Your Product model does not correctly align with the json you are receiving.
The json you've post has weather as a list, but Product assumes it will just be an object. The json parser, then, correctly fails when seeing it is an array in the actual json instead of a JSON object.
The fix should be simple; Product.Weather should be of type List<Weather> (or IEnumerable<Weather> or Weather[], whichever fits your needs).

Blazor: Cannot deserialize the current JSON object

I am creating an app in Blazor, upon getting a list of data displayed(Index View) I am running into the following error:
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
I have heard that Blazor does not support HttpClient for Injection. So I wrote a custom HttpClient to do the same job, however I ran into an issue and unable to understand why I am getting it.
Below is a copy of my entity section:
public class Tournament
{
[Key]
public int TournamentID { get; set; }
public string TournamentName { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
A copy of my code section in razor page:
#code {
Tournament[] tournaments;
string baseUrl;
protected override async Task OnInitializedAsync()
{
baseUrl = AppSettingsService.GetBaseUrl();
tournaments = await Http.GetJsonAsync<Tournament[]>(baseUrl + "api/tournaments/gettournaments");
}
}
And a copy of my custom http client class:
public async Task<T> GetJsonAsync<T>(string requestUri)
{
HttpClient httpClient = new HttpClient();
var httpContent = await httpClient.GetAsync(requestUri);
string jsonContent = httpContent.Content.ReadAsStringAsync().Result;
T obj = JsonConvert.DeserializeObject<T>(jsonContent);
httpContent.Dispose();
httpClient.Dispose();
return obj;
}
Can anyone help me further in this regard?
Thanks.

Restsharp: Deserialize json object with less/more fields than some class

I'm using Restsharp to deserialize some webservice responses, however, the problem is that sometimes this webservices sends back a json response with a few more fields. I've manage to come around this so far by adding all possible field to my matching model, but this web service will keep adding/removing fields from its response.
Eg:
Json response that works:
{
"name": "Daniel",
"age": 25
}
Matching model:
public class Person
{
public string name { get; set; }
public int age { get; set; }
}
This works fine: Person person = deserializer.Deserialize<Person>(response);
Now suppose the json response was:
{
"name": "Daniel",
"age": 25,
"birthdate": "11/10/1988"
}
See the new field bithdate? Now everything goes wrong. Is there a way to tell to restsharp to ignore those fields that are not in the model?
If there's that much variation in the fields you're getting back, perhaps the best approach is to skip the static DTOs and deserialize to a dynamic. This gist provides an example of how to do this with RestSharp by creating a custom deserializer:
// ReSharper disable CheckNamespace
namespace RestSharp.Deserializers
// ReSharper restore CheckNamespace
{
public class DynamicJsonDeserializer : IDeserializer
{
public string RootElement { get; set; }
public string Namespace { get; set; }
public string DateFormat { get; set; }
public T Deserialize<T>(RestResponse response) where T : new()
{
return JsonConvert.DeserializeObject<dynamic>(response.Content);
}
}
}
Usage:
// Override default RestSharp JSON deserializer
client = new RestClient();
client.AddHandler("application/json", new DynamicJsonDeserializer());
var response = client.Execute<dynamic>(new RestRequest("http://dummy/users/42"));
// Data returned as dynamic object!
dynamic user = response.Data.User;
A simpler alternative is to use Flurl.Http (disclaimer: I'm the author), an HTTP client lib that deserializes to dynamic by default when generic arguments are not provided:
dynamic d = await "http://api.foo.com".GetJsonAsync();
In both cases, the actual deserialization is performed by Json.NET. With RestSharp you'll need to add the package to your project (though there's a good chance you have it already); Flurl.Http has a dependency on it.

Categories