Skip to content

[System.Text.Json] Empty string is not deserializing to null #34310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
TanvirArjel opened this issue Mar 31, 2020 · 13 comments
Closed

[System.Text.Json] Empty string is not deserializing to null #34310

TanvirArjel opened this issue Mar 31, 2020 · 13 comments

Comments

@TanvirArjel
Copy link

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."

@AronParker
Copy link

An empty string is not valid JSON according to the spec so the error message is technically correct or am I missing something here?

@TanvirArjel
Copy link
Author

@AronParker Then why System.Text.Json serializing null to empty string in ASP.NET Core Web API method if vice versa is not possible?

@jozkee
Copy link
Member

jozkee commented Mar 31, 2020

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
Copy link

AronParker commented Mar 31, 2020

@AronParker Then why System.Text.Json serializing 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?

@TanvirArjel
Copy link
Author

TanvirArjel commented Mar 31, 2020

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}");
}

@AronParker
Copy link

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.

@jozkee
Copy link
Member

jozkee commented Mar 31, 2020

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.

@jozkee
Copy link
Member

jozkee commented Mar 31, 2020

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

@rynowak
Copy link
Member

rynowak commented Mar 31, 2020

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. dotnet/aspnetcore#8847

@pranavkm - do we have a good solution/workaround for this until we fix the issue?

@rynowak
Copy link
Member

rynowak commented Mar 31, 2020

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.

@jozkee
Copy link
Member

jozkee commented Apr 1, 2020

@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.

@jozkee jozkee closed this as completed Apr 1, 2020
@jozkee jozkee removed the untriaged New issue has not been triaged by the area owner label Apr 1, 2020
@jozkee jozkee added this to the 5.0 milestone Apr 1, 2020
@TanvirArjel
Copy link
Author

@rynowak It wold be great if this is fixed in milestone 5.0 as this is very crucial bug. Thank you.

@pranavkm
Copy link
Contributor

pranavkm commented Apr 1, 2020

@pranavkm - do we have a good solution/workaround for this until we fix the issue?

The workaround here - dotnet/aspnetcore#8847 (comment) 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.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants