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
[]
forBar
, like also suggested here; that alone would break A and B though MissingMemberHandling
; not sureInclude
would lead tonull
overriding a default value; and doesSystem.Text.Json
even have anything like this?NullValueHandling
; same questions as forMissingMemberHandling
- A custom converter for
MySettings
could maybe do it if keep the default to benull
and get to take my own look at theJsonElement
when the property is parsed; I don’t know where to setJsonSerializerOptions
forMicrosoft.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
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:A bit trickier part is the way to tell
null
fromundefined
. 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. Bothobject == undefined
andobject == null
returntrue
, no matter if theobject
isnull
orundefined
. To tellnull
fromundefined
, 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:
If
unknown
was never defined anywhere, this is not the same thing asundefined
. This line will throw an exception. The only working thing istypeof
:typeof unknown
returns the string"undefined"
(not the objectundefined and not its type!
. Note that this is not a defined object likeconst known = undefined
.You can check it and avoid the dangerous call by checking it:
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.
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 tonull
; see source code on GitHub.A possible solution to achieve your goal is to inspect the
MySettings
configuration section as aDictionary<string, object>
. When applicable, adjust the value of theBar
property on themySettings
instance returned from below statement that you are already using.TryGetValue
method of the dictionary will return false.Bar
. This is also a rather unexpected behavior of theJsonConfigurationFileParser
.]
Bar
isnull
.]
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 possiblenull
value for propertyBar
needs to be replaced by an empty array.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.