Empty string is not deserializing to null in System.Text.Json. Look at the following code:
string responseString = await response.Content.ReadAsStringAsync(); // Here responseString is a empty string
EmployeeDetailsViewModel employeeDetails = JsonSerializer.Deserialize<EmployeeDetailsViewModel>(responseString, JsonSerializerOptions); // Here employee should be null but instead throwing an exception
In the above code responseString is getting an empty string because API Controller method returning null and hence so after deserializing the responseString employeeDetails in the above code should be null but instead it's throwing the following exception:
In async the System.Text.Json throw "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. LineNumber: 0 | BytePositionInLine: 0."
An empty string is not valid JSON according to the spec so the error message is technically correct or am I missing something here?
@AronParker Then why System.Text.Json serializing null to empty string in ASP.NET Core Web API method if vice versa is not possible?
As @AronParker just pointed out, the reason why Deserialize fails is because an empty string is not valid JSON, if you want to get null from Deserialization, you would need to pass-in a null JSON token.
why System.Text.Json serializing null to empty string in ASP.NET Core Web API
I am not very familiar with ASP.NET; could you please provide a repro to easily understand your scenario?
Maybe there is something interfering with serialization since that is not the default behavior of System.Text.Json.
@AronParker Then why
System.Text.Jsonserializing null to empty string in ASP.NET Core Web API method if vice versa is not possible?
I am unable to reproduce this behavior of serializing null returning an empty string.
var s = JsonSerializer.Serialize<string>(null);
Debug.Assert(s == "null" && s != "");
Would you mind providing a minimal working example to reproduce that behavior?
Okay! Conside the following ASP.NET Core Web API controller action method:
[Route("api/[controller]/[action]")]
[Produces("application/json")]
[ApiController]
public class EmployeeController : ControllerBase
{
[HttpGet("{employeeId}")]
public async Task<IActionResult> GetEmployeeDetails(int employeeId)
{
// Assume employee details is null
return Ok(null);
}
}
Now call the above API method as follows:
public async Task<EmployeeDetailsViewModel> GetEmployeeDetailsAsync(int employeeId)
{
HttpResponseMessage response = await _httpClient.GetAsync($"employee/get-employee-details/{employeeId}");
if (response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync(); // Here responseString is gettng empty string
EmployeeDetailsViewModel employee = JsonSerializer.Deserialize<EmployeeDetailsViewModel>(responseString, JsonSerializerOptions); // So deserialization is failing here.
return employee;
}
throw new ApplicationException($"{response.ReasonPhrase}: The status code is: {(int)response.StatusCode}");
}
I am unable to test ASP.NET Core Web related code right now but if await response.Content.ReadAsStringAsync() returns "" instead of "null" this is definitely invalid, however this rather seems to be an error of the ASP.NET Core Web part than System.Text.Json.
Thanks for the code-snippet.
If you check your response status; it contains 204 - No content, so I think returning an empty string is just how ReadAsStringAsync works when there is nothing to read, if you call ReadAsByteArrayAsync instead, you will get zero bytes, which makes sense.
I don't think that ASP.NET might have to change null serialization in order to return the proper bytes to represent a null JSON token, that would be potentially invalid since the response code indicates that there is nothing to read. I would think that the user might need to inspect for the No Content code and handle that instead.
cc @rynowak and @pranavkm on this.
Now this makes me wonder, how should the new JSON extension for HttpContent behave when there is no content to read? They currently throw with the same error described in this issue.
@terrajobst @rynowak
If you check your response status; it contains 204 - No content, so I think returning an empty string is just how ReadAsStringAsync works when there is nothing to read, if you call ReadAsByteArrayAsync instead, you will get zero bytes, which makes sense.
This is actually a bug in ASP.NET Core that we plan on addressing. https://github.com/dotnet/aspnetcore/issues/8847
@pranavkm - do we have a good solution/workaround for this until we fix the issue?
I think we should also think about what the behavior was in WebApiClient. If we converted 204 to null before then we should probably do it again in the new functionality. There's something to be said for being technically correct wrt the underlying details of the platform, but there's also something to be said for not harming people with minutiae they don't care about.
@rynowak thanks.
@TanvirArjel please refer to the aspnetcore issue mentioned above since that's the real reason of your undesired behavior; I will close this one.
@rynowak It wold be great if this is fixed in milestone 5.0 as this is very crucial bug. Thank you.
@pranavkm - do we have a good solution/workaround for this until we fix the issue?
The workaround here - https://github.com/dotnet/aspnetcore/issues/8847#issuecomment-477319495 works reasonably well, although it has the side-effect of affecting the behavior when a value (not an IActionResult) is returned from the action (eg. public Person GetPerson() => dbContext.Persons.FirstOrDefaultAsync()). If this is important, you can author a result filter that relies on ObjectResult.DeclaredType to disambiguate between implicit and explicitly constructed ObjectResult instances and handle those. If you need help authoring this filter, feel free to ping the linked aspnetcore issue and I can elaborate further.