Writing an API with ASP.NET is a very trivial exercise.

Assuming we have this type:

public record Spy(string FullName, string ActiveAgency, byte OfficialAge, bool Active);

We can write an endpoint that returns a Spy like this:

app.MapGet("/", () =>
    {
        var spy = new Spy("James Bond", "MI-6", 40, true);
        return spy;
    }
);

This is short-hand for the following:

app.MapGet("/", () =>
    {
        var spy = new Spy("James Bond", "MI-6", 40, true);
        return Results.Ok(spy);
    }
);

Here, we explicitly indicate that we are returning the response with an HTTP 200 response.

Both the above will return the following:

{
  "fullName": "James Bond",
  "activeAgency": "MI-6",
  "officialAge": 40,
  "active": true
}

Suppose, for whatever reason, we want to customize this JSON response.

Let us say that the target consumer expects the OfficialAge to be string delimited. And that the target consumer, for some reason, expects the attributes to be lowercase snake_cased, as people in the Python community are used to, as opposed to the default camelCase.

This is trivial to achieve.

Rather than just returning the object or using Results.OK, we use the Results.Json method and pass a configured JsonSerializerOptions object to it.

app.MapGet("/Formatted", () =>
    {
        var spy = new Spy("James Bond", "MI-6", 40, true);
        // Setup our options here
        var options = new JsonSerializerOptions
        {
            // Output numbers as strings
            NumberHandling = JsonNumberHandling.WriteAsString,
            // Use lower case, kebab case
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
        };
        return Results.Json(spy, options);
    }
);

This will return the following:

{
  "full_name": "James Bond",
  "active_agency": "MI-6",
  "official_age": "40",
  "active": true
}

The difference is as follows:

JsonCompare

If you have many endpoints, doing this for every endpoint can get tedious. Plus, it will be a nightmare to maintain if you have to change, as you need to remember to check all the endpoints and update your configuration.

You can configure your application to do this by default by configuring the WebApplicationBuilder:

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<JsonOptions>(options =>
{
  
    options.SerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString;
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
});

Once this is done, all endpoints will use these settings.

You can, however, override them on a case-by-case basis.

Our final program looks like this:

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.Json;

var builder = WebApplication.CreateBuilder(args);
// Configure global json handling
builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString;
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
});
var app = builder.Build();

app.MapGet("/", () =>
    {
        var spy = new Spy("James Bond", "MI-6", 40, true);
        return Results.Ok(spy);
    }
);

app.MapGet("/Formatted", () =>
    {
        var spy = new Spy("James Bond", "MI-6", 40, true);
        // Override the global handling to use UPPER-CASE-KEBAB
        var options = new JsonSerializerOptions
        {
            // Output numbers as strings
            NumberHandling = JsonNumberHandling.Strict,
            // Use lower case, kebab case
            PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
        };
        return Results.Json(spy, options);
    }
);

app.Run();

If we run the default path, /, we get the following response:

{
  "full_name": "James Bond",
  "active_agency": "MI-6",
  "official_age": "40",
  "active": true
}

If we run the /Formatted, we get the following:

{
  "FULL-NAME": "James Bond",
  "ACTIVE-AGENCY": "MI-6",
  "OFFICIAL-AGE": 40,
  "ACTIVE": true
}

TLDR

You can configure ASP.NET to control the output of JSON responses, either at the global level or at the endpoint level, using an appropriately configured JsonSerializerOptions object.

The code is in my GitHub.

Happy hacking!