Search code examples
c#jsonmicrosoft.extensions.configurationasp.net-core-9.0

How to distinguish empty/null/unset array property in appsettings.json?


I am using Microsoft.Extensions.Configuration in ASP.NET Core 9.0 to access configurations from the appsettings.json file:

public class MySettings
{
    public int? Foo { get; set; }
    public string[]? Bar{ get; set; }
}

appsettings.json:

{
    [...]
    "MySettings": {
        "Foo": 42,
        "Bar": [
            "a",
            "b",
            "c"
        ]
    }
}

I'm reading this part this way:

var mySettings = config.GetSection("MySettings").Get<MySettings>();

This works well, mySettings.Bar is string[3] = ["a", "b", "c"] as you'd expect.

Now I'm having trouble to distinguish between three other cases in appsettings.json:

A:

{
    [...]
    "MySettings": {
        "Foo": 42
    }
}

B:

{
    [...]
    "MySettings": {
        "Foo": 42,
        "Bar": null
    }
}

C:

{
    [...]
    "MySettings": {
        "Foo": 42,
        "Bar": []
    }
}

For all three cases, mySettings.Bar will become null, but I'd want only A and B to become null, and C to become string[0].

I have found lots of little bits and ideas from a question for Newtonsoft Json.NET but I can't seem to figure out how to cram it all into something that works with Microsoft.Extensions.Configuration:

  • Default value [] for Bar, like also suggested here; that alone would break A and B though
  • MissingMemberHandling; not sure Include would lead to null overriding a default value; and does System.Text.Json even have anything like this?
  • NullValueHandling; same questions as for MissingMemberHandling
  • A custom converter for MySettings could maybe do it if keep the default to be null and get to take my own look at the JsonElement when the property is parsed; I don't know where to set JsonSerializerOptions for Microsoft.Extensions.Configuration
  • Some attributes on Bar itself; but which? System.Text.Json doesn't have a lot to begin with

It is crucial for me to be able to differentiate between A and C. B should preferably become null since that's what the file says, but I could cope with that becoming string[0] as well, too.


Solution

  • Appsettings are not being handled by json (de)serialization, but by the JsonConfigurationFileParser which uses custom parsing.

    This JsonConfigurationFileParser has private built-in logic to set empty arrays to null; see source code on GitHub.


    A possible solution to achieve your goal is to inspect the MySettings configuration section as a Dictionary<string, object>. When applicable, adjust the value of the Bar property on the mySettings instance returned from below statement that you are already using.

    var mySettings = config.GetSection("MySettings").Get<MySettings>();
    
    • For scenario A, the TryGetValue method of the dictionary will return false.
    • For scenario B, the dictionary holds an empty string value for key Bar. This is also a rather unexpected behavior of the JsonConfigurationFileParser.

    scenario B

    • For scenario C, the value for dictionary entry with key Bar is null.

    scenario C

    That gives below code, which checks whether the Bar key is present and whether the value is not an empty string. If so, either an array with values or an empty one has been configured and a possible nullvalue for property Bar needs to be replaced by an empty array.

    var mySettings = config.GetSection("MySettings").Get<MySettings>();
    
    var section = config.GetRequiredSection("MySettings").Get<Dictionary<string, object>>()!;
    if (section.TryGetValue("Bar", out var value)
        && value is not string
        )
    {
        mySettings.Bar ??= [];
    }