jsonazure-functionsjson-deserialization.net-9.0

Case insensitive JSON parameter names in FunctionsApplicationBuilder


I have scaffolded an Azure Functions application using .NET 9. The Program.cs file looks like this:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Services
    .AddApplicationInsightsTelemetryWorkerService()
    .ConfigureFunctionsApplicationInsights();

builder.Build().Run();

Inspecting the code reveals ConfigureFunctionsWebApplication calls Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.AddFunctionsWorkerDefaults which configures default JSON serialisation to ignore property name case.

This is not the behaviour I am experiencing. My endpoint is configured to accept the following JSON body:

{
  "Double": 2
}

If the parameter name is lowercase, the JSON deserialization fails and the value is defaulted to zero.

I found this post suggesting either Microsoft.AspNetCore.Http.Json.JsonOptions or Microsoft.AspNetCore.Mvc.JsonOptions needs to be configured. I don't believe this should be necessary and neither option worked for me anyway. .NET 8 Azure Functions: setting System.Text.JsonSerializer defaults

Any suggestions? I have committed sample code here https://github.com/DangerousDarlow/AzureFunctionsExplore

The function code is https://github.com/DangerousDarlow/AzureFunctionsExplore/blob/master/AzureFunctionsExplore/Math.cs

public class Math(ILogger<Math> logger)
{
    [Function("Double")]
    [OpenApiOperation("Double")]
    [OpenApiRequestBody("application/json", typeof(Body), Required = true)]
    [OpenApiResponseWithBody(HttpStatusCode.OK, "'application/json'", typeof(Body))]
    public async Task<IActionResult> Double([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest request)
    {
        var requestBodyString = await new StreamReader(request.Body).ReadToEndAsync();
        var requestBody = JsonSerializer.Deserialize<Body>(requestBodyString);
        logger.LogInformation("Doubling number: {number}", requestBody?.Double);
        return new OkObjectResult(new Body (requestBody?.Double * 2 ?? 0 ));
    }

    public record Body(int Double);
}

Solution

  • After further investigation I have concluded that function ConfigureFunctionsWebApplication does initialise JSON serialization with sensible options including case insensitive parameter names however I was not using these options with the serializer in my endpoint implementation. I needed to inject them into the class implementing the endpoint and pass them to the serializer.

    public class Math(IOptions<JsonSerializerOptions> jsonSerializerOptions, ILogger<Math> logger)
    {
        private readonly JsonSerializerOptions _jsonSerializerOptions = jsonSerializerOptions.Value;
        
        [Function("Double")]
        [OpenApiOperation("Double")]
        [OpenApiRequestBody("application/json", typeof(Body), Required = true)]
        [OpenApiResponseWithBody(HttpStatusCode.OK, "'application/json'", typeof(Body))]
        public async Task<IActionResult> Double([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest request)
        {
            var requestBodyString = await new StreamReader(request.Body).ReadToEndAsync();
            var requestBody = JsonSerializer.Deserialize<Body>(requestBodyString, _jsonSerializerOptions);
            logger.LogInformation("Doubling number: {number}", requestBody?.Double);
            return new OkObjectResult(new Body (requestBody?.Double * 2 ?? 0 ));
        }
    
        public record Body(int Double);
    }
    

    It may also be possible to configure the default options used by the static JsonSerializer instance however I haven't explored this further as injecting the options suits my purpose and it's clear.