Trouble deserializing JSON to a C# object [duplicate] - c#

This question already has answers here:
System.Text.Json.JsonException: The JSON value could not be converted
(4 answers)
Closed 12 days ago.
I am working on a WPF MVVM application to retrieve some cryptocurrency information from this API. I am able to call the API and get an HTTP response, however, I am having trouble deserializing this response to an object. I understand that the symbol variable is passed but not used, however, I want the deserialization process to work and then I will format the URI accordingly to include the symbol and the API Key. Here is the code:
Crypto Object
public class Crypto
{
public string? Symbol { get; set; }
public string? Name { get; set; }
public double? Price { get; set; }
public double? ChangesPercentage { get; set; }
}
API Call Service Interface
public interface ICryptoService
{
Task<Crypto> GetCrypto(string symbol);
}
API Call Service
public async Task<Crypto> GetCrypto(string symbol)
{
using (HttpClient client = new HttpClient())
{
using var response = await client.GetAsync("https://financialmodelingprep.com/api/v3/quote/BTCUSD?apikey=KEY", HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
if (response.Content is object && response.Content.Headers.ContentType.MediaType == "application/json")
{
var responseStream = await response.Content.ReadAsStreamAsync();
try
{
return await System.Text.Json.JsonSerializer.DeserializeAsync<Crypto>(responseStream, new System.Text.Json.JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
catch (JsonException)
{
Console.WriteLine("Invalid JSON!");
}
}
else
{
Console.WriteLine("HTTP Response cannot be deserialised");
}
return null;
}
}
}
Main Method
CryptoService cryptoService = new CryptoService();
cryptoService.GetCrypto("BTCUSD").ContinueWith((task) =>
{
var crypto = task.Result;
});
I am attaching the JSON response that the link will provide below:
[
{
"symbol": "BTCUSD",
"name": "Bitcoin USD",
"price": 22887.08,
"changesPercentage": -0.1263,
"change": -28.9473,
"dayLow": 22887.08,
"dayHigh": 23351.51,
"yearHigh": 48086.836,
"yearLow": 15599.047,
"marketCap": 441375461059,
"priceAvg50": 19835.04,
"priceAvg200": 19730.518,
"volume": 27292504064,
"avgVolume": 23965132574,
"exchange": "CRYPTO",
"open": 23267.4,
"previousClose": 23267.4,
"eps": null,
"pe": null,
"earningsAnnouncement": null,
"sharesOutstanding": 19284918,
"timestamp": 1675872360
}
]
This is the exception that I get whenever I run the code:
Exception thrown: 'System.Text.Json.JsonException' in System.Private.CoreLib.dll

Your json is a json array, not a single json object, so you need to deserialize to a collection:
var result = await JsonSerializer.DeserializeAsync<List<Crypto>>(...);
P.S.
In general case it is recommended to just await async functions instead of using ContinueWith.

Related

Cannot correctly parse my object from Json returned from my C# Web API

I have searched until my eyes are bleeding but alas no answers.
I have a class called Booking.
public class Booking
{
[Key]
public int Id { get; set; }
[Required]
public int RoomNumber { get; set; }
[Required]
public string? ClientName { get; set; }
}
The API method is
[HttpGet]
public async Task<JsonResult> Get(int id)
{
Booking? booking = await _bookingRepository.FindByIdAsync(id);
if (booking == null)
{
return new JsonResult(NotFound());
}
return new JsonResult(Ok(booking));
}
The Client is
protected static async Task<Booking?> GetAsync<Booking>(HttpClient httpClient)
{
HttpResponseMessage response = await httpClient.GetAsync("https://localhost:7139/api/HotelBooking?id=5");
if (response.StatusCode == HttpStatusCode.NoContent)
{
string msg = await response.Content.ReadAsStringAsync();
return default(Booking);
}
else if (response.IsSuccessStatusCode)
{
string msg = await response.Content.ReadAsStringAsync();
Booking? booking = JsonConvert.DeserializeObject<Booking>(msg);
//Booking? booking = await response.Content.ReadFromJsonAsync<Booking>();
return booking;
}
else
{
string msg = await response.Content.ReadAsStringAsync();
Console.WriteLine(msg);
throw new Exception(msg);
}
}
The value in msg is
{
"Value": {
"Id": 5,
"RoomNumber": 100,
"ClientName": "Nelms, Anthony"
},
"Formatters": [],
"ContentTypes": [],
"DeclaredType": null,
"StatusCode": 200
}
The result: I have a Booking object that is instantiated by the JsonConvert.DeserializeObject line of code, but all three values are missing. The string is null and the two ints are zero.
I believe the problem is that the section "Value" is "hiding" my data. I am not sure how to read the data from response.Content.
This is my first attempt at Http. Any help would be appreciated.
Can you try replacing msg with msg.Result in below line?
JsonConvert.DeserializeObject<Booking>(msg.Result);
I tried the suggestion from #TopSail in the comments, and it worked:
Well, I would try return Ok(booking) instead of JsonResult(Ok(booking)) - see if that works. Seems like wrapping the return in a JsonResult is also creating a new type of OK object here. –
Before I read your suggestion I simply returned the object as follows:
return booking;
That worked.
However, I like your suggestion better. I implemented your suggestion as follows:
return Ok(booking);
This also worked.

How to copy Stream property straight to response body on JSON serialization

I want to optimize response returning from ASP.NET Core application. In order to do that I'm serializing the object to JSON UTF-8 bytes and inserting it to SQL as varbinary(max). I initially though that I'll get a stream from the column value using ADO.NET and just return it from Controller. This worked perfectly fine at the beginning as I've simply returned the Stream from the controller and it worked:
public async Task<Stream> GetSingleObject()
{
Response.ContentType = "application/json; charset=UTF-8";
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
return streamWithUtf8JsonBytesFromDb;
}
Unfortunately I have to attach additional properties to the response object:
{
"metadata": null,
"data": (here I would like to copy bytes from SQL stream directly to the response without any transformation)
"links": [
"self": "some url"
]
}
How can I use System.Text.Json to achieve this result in performant and usable fashion?
I've came up with two approaches so far, but I think there has to be a better way:
First solution:
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task GetSingleObject()
{
Response.ContentType = "application/json; charset=UTF-8";
await Response.WriteAsync("{\"meta\":null,\"data\":", System.Text.Encoding.UTF8)
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
await streamWithUtf8JsonBytesFromDb.CopyToAsync(Response.Body)
await Response.WriteAsync($",\"links\":[{{\"self\":{Request.GetEncodedUrl()}}}]}}", System.Text.Encoding.UTF8);
}
Constructing JSON parts from string and concatenating it in the stream is obviously painful
Second solution:
public class Meta
{ /* Not relevant */ }
public class ObjectResponse
{
public Meta? Meta { get; set; }
public JsonDocument Data { get; set; }
public Dictionary<string, string> Links { get; set; }
}
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task<ObjectResponse> GetSingleObject()
{
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
return new ObjectResponse {
Meta = null,
Data = await JsonDocument.ParseAsync(streamWithUtf8JsonBytesFromDb),
Links = new Dictionary<string, string> {
{ "self", Request.GetEncodedUrl()}
};
}
}
This is better than the first approach, but it adds unnecessary parsing of the stream which I'd like to omit.
What I'd think is the best idea is to use the following:
public class Meta
{ /* Not relevant */ }
public class ObjectResponse
{
public Meta? Meta { get; set; }
public Stream Data { get; set; }
public Dictionary<string, string> Links { get; set; }
}
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task<ObjectResponse> GetSingleObject()
{
return new ObjectResponse {
Meta = null,
Data = await _repository.GetSingleObject(),
Links = new Dictionary<string, string> {
{ "self", Request.GetEncodedUrl()}
};
}
}
But it doesn't work, because System.Text.Json tries to serialize this stream instead of copying it directly to the response body (which makes sense of course). Am I missing something? Is there a custom converter I could use to achieve the result I'd like?

Patch API call in C# doesn't work, the same call works in swagger

I want to use an external API which has Swagger. In Swagger I am calling this url:
PATCH /rest/inventory/item/{id}
with parameters: X-Auth-Token, id and patchOperations which looks like this:
[
{
"op": "replace",
"path": "price",
"value": 6.2
}
]
And when I call this method with those parameters, it works. I get success code 200 and afterwards when I call the GET method I see that the price of the item has been updated to 6.2.
Now I want to do this in C#. I am already calling some GET methods from the same API successfully. This is my code for the PATCH method:
var model = new Dictionary<string, object>
{
{"op", "replace"},
{"path", "price"},
{"value", 6}
};
var blabla = await _httpProvider.PatchAsync($"https://{url}/server/rest/inventory/item/{id}", model, null, null, null, connection.Request.HeaderParameters);
public async Task<HttpResponseModel> PatchAsync<T>(string uri, T data, HttpClientHandler httpClientHandler = null, TimeSpan? timeout = null, string contentTypes = null, Dictionary<string, string> headerParameters = null)
{
using (var client = CreateHttpClient(httpClientHandler, timeout, contentTypes, headerParameters))
{
var requestContent = new StringContent(JsonConvert.SerializeObject(data));
var response = await client.PatchAsync(new Uri(uri), requestContent);
var result = new HttpResponseModel
{
Success = response.IsSuccessStatusCode,
ResponseContent = await response.Content.ReadAsStringAsync(),
ResponseTime = sw.Elapsed
};
return result;
}
}
Where is my mistake? I am getting error StatusCode: 500, ReasonPhrase: 'Server Error', Version: 1.1, Content: System.Net.Http.StreamContent
The mistake is that you're not pasting the same content, not quite anyway.
Your PATCH example is an array of a objects that has 3 properties, in your example there is only 1 element in the array, but it is still an array. Your C# is serialized into single object.
It's subtle but your JSON that you are sending is actually:
{
"op": "replace",
"path": "price",
"value": 6
}
So instead you need to send your dictionary or other object inside an array:
var model = new List<object> {
{
new Dictionary<string, object>
{
{ "op", "replace"},
{"path", "price"},
{"value", 6}
}
};
Ideally, in c# you would create a class to represent this DTO (Data Transfer Object), it can work with anonymous types or with dictionaries (a Dictionary<string,object> serializes into a single JSON object) but the code gets harder to manage over time.
public class DTO
{
public string op { get; set; }
public string path { get; set; }
public object value { get; set; }
}
...
var model = new List<DTO>
{
new DTO {
op = "replace",
path = "price",
value = 6
}
};

How to check if i properly parsed through the JSON?

This is my first time calling an API and parsing through the JSON output. I know I called the API and retrieved the data since I printed it onto the console but I now want to print out a certain part of the JSON for example: all the titles of the list (if it had titles and some other information).
public async Task MainAsync()
{
using (var client = new HttpClient()) //creates a new client that will make the call to the api.
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); // Accepts header for JSON format
try
{
HttpResponseMessage response = await client.GetAsync("http://quickstats.nass.usda.gov/api/api_GET/?key=MYKEYGOESHERE&commodity_desc=CORN&year__GE=2012&state_alpha=VA&format=JSON");
if (response.IsSuccessStatusCode)
{
string contents = await response.Content.ReadAsStringAsync();
Console.WriteLine(contents);
//List<quickStats> quickStat = JsonConvert.DeserializeObject<List<quickStats>>(contents);
quickStats quickStat = JsonConvert.DeserializeObject<quickStats>(contents);
Console.WriteLine("Done");
foreach(var item in quickStat)
{
Console.WriteLine(item.source_desc);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//Console.WriteLine("test");
}
}
}
}
class quickStats
{
public String source_desc { get; set; }
}
The JSON isn't in an array so I can't create a list like in the commented line then do a foreach statement for that list, following this vid: https://www.youtube.com/watch?v=xkB-cIMjXGQ
I am wondering how else I can print the parsed list of "source_desc".
Any help is appreciated!
It's structure like this
{"data":[
{"source_desc": "XXXX"},
{"source_desc": "OOOO"},
{"source_desc": "TTTT"}
]}
so your serialization class should be defined as:
class quickStats
{
public List<quickStatsDetail> data { get; set; }
}
class quickStatsDetail
{
public String source_desc { get; set; }
}

How to store API result set for different response types using C#.Net

I am trying to call two API's and store their response.
Below is the API response type:
API1 response:
{
"Name": "Apple",
"Expiry": "2008-12-28T00:00:00",
"Price": 3.99,
"Sizes": [
"Small",
"Medium",
"Large"
]
}
API2 Response:
["Name=xyz, Id=1, Version=1","Name=abc, Id=1, Version=2","Name=hgf, Id=1, Version=3","Name=utgds, Id=1, Version=4","Name=kfgf, Id=2, Version=1"]
below is the code to call an API and get the result set.
var jsonObj = Get<SomeClass>("API_URL1");
var jsonObj2 = Get<Test>("API_URL2");
public T Get<T>(string requestUri)
{
T response = default(T);
using (var handler = new HttpClientHandler { Credentials = CredentialCache.DefaultNetworkCredentials })
{
using (var httpClient = CreateNewRequest(handler))
{
var httpTask = httpClient.GetAsync(requestUri);
var response = task.Result;
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
var res = JsonConvert.DeserializeObject<T>(result);
}
}
}
return res;
}
private HttpClient CreateNewRequest(HttpClientHandler handler)
{
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public class Test
{
public string Name { get; set; }
public int Id { get; set; }
public int Version { get; set; }
}
Similarly i have created a class to hold the API1 response.
I am able to store API1 Response however while storing API2 response i am getting this error message.
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type
because the type requires a JSON object (e.g. {"name":"value"}) to
deserialize correctly. To fix this error either change the JSON to a
JSON object (e.g. {"name":"value"}) or change the deserialized type to
an array or a type that implements a collection interface..
any idea/hints on this?
Thanks!
I tested what i said in comment. Second response is JSON array of strings. You should deserialize it as array of string (string[]) or collection of strings (List<string>). Following works as expected:
var response = "[\"Name=xyz, Id=1, Version=1\", \"Name=abc, Id=1, Version=2\", \"Name=hgf, Id=1, Version=3\", \"Name=utgds, Id=1, Version=4\", \"Name=kfgf, Id=2, Version=1\"]";
var result = JsonConvert.DeserializeObject<string[]>(response);
To summarise: instead of Get<Test> use Get<string[]>. You just have to parse strings, but this seems to be another problem.
Full demo below (url invocations intentionally mocked):
class Program
{
static void Main(string[] args)
{
var invoker = new JsonMockInvoker();
var jsonObj = invoker.Get<SomeClass>("API_URL1");
var jsonObj2 = invoker.Get<string[]>("API_URL2");
}
}
class SomeClass
{
public string Name { get; set; }
//other properties
}
public class JsonMockInvoker:JsonInvoker
{
public override string InvokeRest(string url)
{
if (url == "API_URL1")
return "{\"Name\": \"Apple\",\"Expiry\": \"2008-12-28T00:00:00\",\"Price\": 3.99,\"Sizes\": [\"Small\",\"Medium\",\"Large\"]}";
if (url == "API_URL2")
return "[\"Name=xyz, Id=1, Version=1\", \"Name=abc, Id=1, Version=2\", \"Name=hgf, Id=1, Version=3\", \"Name=utgds, Id=1, Version=4\", \"Name=kfgf, Id=2, Version=1\"]";
throw new NotImplementedException();
}
}
public class JsonInvoker
{
public T Get<T>(string requestUri)
{
var result = InvokeRest(requestUri);
return result != null ? JsonConvert.DeserializeObject<T>(result) : default(T);
}
public virtual string InvokeRest(string url)
{
using (var handler = new HttpClientHandler { Credentials = CredentialCache.DefaultNetworkCredentials })
using (var httpClient = CreateNewRequest(handler))
{
var httpTask = httpClient.GetAsync(url);
var response = httpTask.Result;
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsStringAsync().Result;
}
}
return null;
}
private HttpClient CreateNewRequest(HttpClientHandler handler)
{
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
}
String values are not in JSON format - you have to parse them manually:
//parse to typed object
var parsedObject = jsonObj2.Select(a => new
{
Id = Regex.Match(a, "(?<=Id=)[^,]*").Value,
Name = Regex.Match(a, "(?<=Name=)[^,]*").Value
});
//parse to dictionary
var regex = new Regex("([\\s]|^)(?<key>[^=]+)=(?<value>[^,]*)");
var parsed = jsonObj2.Select(a =>
{
var dictionary = new Dictionary<string, string>();
foreach (Match match in regex.Matches(a))
dictionary[match.Groups["key"].Value] = match.Groups["value"].Value;
return dictionary;
});

Categories