skip to Main Content

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.

2

Answers


  1. This is pretty simple and yet absolutely fundamental stuff for everyone. First, the question is not related specifically to JSON. This is about comparing 4 essentially different cases. Yes, I added yet another case, when Bar is not an array. Please run this snippet:

    const A = {
        "MySettings": {
            "Foo": 42
        }
    };
    
    const B = {
        "MySettings": {
            "Foo": 42,
            "Bar": null
        }
    };
    
    const C = {
        "MySettings": {
            "Foo": 42,
            "Bar": "some value"
        }    
    }
    
    const D = {
        "MySettings": {
            "Foo": 42,
            "Bar": []
        }    
    }
    
    const SortOut = (name, object) => {
        console.log("=============== Analizing object ${name}:");
        if (object && object.MySettings) {
            if (object.MySettings.Bar == null) {
                console.log("object.MySettings.Bar is null or undefined");
                if (object.MySettings.Bar === undefined)
                    console.log("More exactly, object.MySettings.Bar is undefined");
                else if (object.MySettings.Bar === null)
                    console.log("More exactly, object.MySettings.Bar is null");
            } else {
                if (object.MySettings.Bar.constructor != Array)
                    console.log("object.MySettings.Bar is not an array");
                else 
                    console.log(`object.MySettings.Bar is an array of the length ${object.MySettings.Bar.length}`);
            }
        }
    };
    
    SortOut("A", A);
    SortOut("B", B);
    SortOut("C", C);
    SortOut("D", D);

    A bit trickier part is the way to tell null from undefined. The objects are essentially different, you can assign one or another value to the object and take into account that they are different.

    However, in both cases are interchangeable if you use the == comparison. This is the result of automatic type conversion. Both object == undefined and object == null return true, no matter if the object is null or undefined. To tell null from undefined, use the comparison operator ===.

    Finally, let’s consider the most unpleasant case you did not ask about. All of the above work for object properties. But there is also a case of a variable on the global level.

    Consider this:

    console.log(unknown);
    

    If unknown was never defined anywhere, this is not the same thing as undefined. This line will throw an exception. The only working thing is typeof: typeof unknown returns the string "undefined" (not the object undefined and not its type!. Note that this is not a defined object like const known = undefined.

    You can check it and avoid the dangerous call by checking it:

      if (typeof unknown != `${undefined}`)
      // not the same as let unknown = undefined !!!
         console.log(unknown);
    

    Does it make sense to do such things? No! Just the opposite, you should better let it go and get an exception, it will help to get rid of all variables that were never defined during the development, because they are absolute evil.

    Login or Signup to reply.
  2. 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>>()!;
    var hasValue = false;
    if (section.TryGetValue("Bar", out var value))
    {
        hasValue = value is not string;
    }
    
    if (hasValue)
    {
        mySettings.Bar ??= [];
    }
    

    I leave it as an exercise for you to translate above to a reusable function, preferably going over all array/collection properties in one go. At least you have a way forward to dectect the different scenarios.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search