1

Consider the following minimal repro example using the default ASP.NET Core template:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Content-Type application/json, Body: true
app.MapGet("/True", () => Results.Json(true));

// Content-Type application/json, Body: "string"
app.MapGet("/String", () => Results.Json("string"));

// Content-Type application/json, Body: {"someProperty":"someValue"}
app.MapGet("/Object", () => Results.Json(new { SomeProperty = "someValue" }));


// expected: Content-Type application/json, Body: null
// actual: no Content-Type header, zero-length Body
app.MapGet("/Null", () => Results.Json(null));


app.Run();

As you can see, Results.Json always yields a Content-Type of application/json and a body containing the json-serialized version of the first argument, except in the case of null (which is annoying, because it means I need to treat this special case in my client differently as well).

I know that I can work around this by creating my own version of Results.Json, but I'd like to understand why this happens. After all, the developers of the Results class apparently went the extra mile to implement a special-case logic for null, so they might have had a good reason for doing that (which I don't understand yet). Unfortunately, the documentation has not been helpful.


Note:

  • I know that I don't need Results.Json for the first (/True) and the third (/Object) case. I do need it for the second case, because otherwise ASP.NET will return text (Content-Type text/plain) instead of a JSON string, which would mean yet another special case in my client.
  • app.MapGet("/Null2", () => (object?)null); does return the expected result (a JSON-encoded null).
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • I suppose, why do you need a content type when there is no content? Sounds like a question for the devs perhaps. – DavidG Mar 13 '23 at 10:06
  • 1
    Note that, if nothing else, it's *consistent*, in that all of the result returning implementations exit early if the value they've been asked to write is `null`. – Damien_The_Unbeliever Mar 13 '23 at 10:06
  • @Damien_The_Unbeliever: Well, `app.MapGet("/Null2", () => (object?)null);` *does* return the expected result (a JSON-encoded `null`). I added this observation to the question, in case it helps. – Heinzi Mar 13 '23 at 10:08
  • I meant all of the implementations you'll get by calling methods on the `Results` class - e.g. `BadResult` and `Content` do this same thing. – Damien_The_Unbeliever Mar 13 '23 at 10:09
  • Why this happens? Because that's how `JsonHttpResult` object is implemented. Null is ignored, not written to body, and it doesn't set content-type. We can only guess motivation behind such decision. Whatever it was I believe it is a mistake, it is inconsistent with how Json works. – freakish Mar 13 '23 at 10:19
  • 1
    @DavidG well, you may ask: why do we need `null` Json to begin with? And actually I do think it is redundant. But it exists, and someone actually may treat `null` differently from empty content. Otherwise what's the point of `null`? Therefore I strongly believe that `Results.Json` should handle it consistently with Json spec. – freakish Mar 13 '23 at 10:28

2 Answers2

3

I want to body to contain the JSON literal null

This can't be done via Results.Json because it explicitly does nothing for null value - source code.

You can return null as object:

app.MapGet("/Null", () => (object?)null);

Or explicitly serialize and return content:

app.MapGet("/Null", () =>
    Results.Content(JsonSerializer.Serialize<string?>(null), MediaTypeHeaderValue.Parse("application/json")));

As for why - I can only guess here - mine would be "perfromance", maybe there is a case in TechEmpower benchmarks which allows such return, so shortcutting makes sense =)

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
2

The only other way I could figure out how to get the null JSON value was using JsonElement:

var jsonNull = JsonDocument.Parse("null").RootElement;

app.MapGet("/", () => Results.Json(jsonNull));
davidfowl
  • 37,120
  • 7
  • 93
  • 103