aspnet / Configuration

[Archived] Interfaces and providers for accessing configuration files. Project moved to https://github.com/aspnet/Extensions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ConfigurationBinder binds empty array as null

andrewslavin opened this issue · comments

Example:

  1. I have a class AppSettings with property public string[] SomeValues { get; set; }.
  2. My appsettings.json is {"SomeValues": []}.

When I bind an instance of AppSettings to the configuration like this: configurationBuilder.AddJsonFile("appsettings.json").Build().Bind(appSettings), property SomeValues is null. I expect empty array.

Note that if I change appsettings.json to {"SomeValues": ["val1", "val2"]}, SomeValues does get set to the expected string array. So generally binding is working.

I think the binder doesn't tough a property when the configuration source doesn't yield any value for the property.
SomeValue is null because you didn't initialize it. Try

public string[] SomeValues { get; set; } = new string[0];

Thanks @MichaCo. This solves the specific issue I described, but creates another. If property is declared as public string[] SomeValues { get; set; } = new string[0]; and appsettings.json is {"SomeValues": null}, SomeValues stays as empty array. Expected null.

I can obviously work around both scenarios, but the expected behaviour is that I do not have to give default values to the properties, and ConfigurationBinder sets them to whatever I see in appsettings.json (provided there are no other config sources of course).

This mostly just due to how config works, you can't store null values in config today, as there's no way to determine if config has a "key" just with a null value today. So the binder works as @MichaCo describes, it basically just uses reflection on properties and checks if there is a value in the config with the matching key, if a null value is returned, that property is left alone.

@HaoK thank you for the detailed explanation. I have one last question:

  1. Is this behaviour of ConfigurationBinder fundamentally impossible to change because of how reflection works (or some other reasons), or
  2. It is possible to change, but you think the issue it causes is not worth the effort, or
  3. It is possible to change and you will accept it to be implemented as an improvement?

We are unlikely to change the binder at this point no. What behavior are you looking for?

What behavior are you looking for?

As I described above, the naturally expected behaviour is "what you see is what you get". If appsettings.json has SomeValues set to null or empty array, I expect the SomeValues property of my AppSettings class to be set to null or empty array correspondingly.

That should happen even if the property has been declared with a default, e.g. public string[] SomeValues { get; set; } = new string[] {"val1", "val2"};. The property should retain its default only if appsettings.json does not contain the SomeValues key at all.

How would you imagine this working if config doesn't let you distinguish between "SomeValues" = null and it just never being set?

If that ability was added to config, then you could ask for the appropriate binder change

@HaoK here's the behaviour I expect:

  1. Whatever is in the json file, gets applied to the class properties, including nulls and empty arrays.
  2. If there's a property in the class that does not have a matching key in the json file, the property retains its default value if defined, or it will be the default of the type if not defined.

How would you imagine this working if config doesn't let you distinguish between "SomeValues" = null and it just never being set?

I do not think I want or need to be able to distinguish. When I define SomeValues in the class, I know whether it's nullable or not, and if not, what the default of the type is. Then I can decide on the behaviour I want if SomeValues is not present in json or if it's present as null.

Here are some examples of what I would expect:

  1. SomeValues is nullable type with no default. If it's not present in the json file, or present as null, the prop value is null.
  2. SomeValues is nullable type with a non-null default. If it's not present in the json file, the prop is the defined default. If it is present in the json file as null, the prop value is null.
  3. SomeValues is non-nullable type with no default. If it's not present in the json file, the prop is the default of type. If it is present in the json file as null, I get a runtime error.
  4. SomeValues is non-nullable type with a default. If it's not present in the json file, the prop is the defined default. If it is present in the json file as null, I get a runtime error.

Hope the above makes sense.

Yeah I don't think that's an improvement over todays behavior, that would basically result in every property being null if its not defined in config.

Not sure what you mean, that's not the improvement I'm asking for. It already works like that right now:

  • every nullable property not defined in config will be null,
  • every not nullable property not defined in config will be default of type.

@HaoK I feel there's still a lack of understanding of what I'm asking for, so I'll try to explain again, in a slightly different way:

  1. When a class gets instantiated, all its properties get set to their relative defaults of type. And those properties that have explicitly defined default values, get set to those default values.
  2. Now as we have an instance of the class, we start updating its properties from config. For each property:
  3. If config does NOT have a key that matches the prop by name, we leave the prop value as is.
  4. If config DOES have a key that matches the prop by name, we update the prop value to the value in the config.

The last scenario above is currently not working as expected. We currently do NOT update the prop value to the value in the config, if the value in the config is null or empty array.

As I said before, there's no way for you to make the binder work like this (3 vs 4) without the ability to tell if a config key is defined and null vs just not set. If you feel you can make this work, feel free to submit a PR demonstrating this and we can discuss from there if you like

You don't actually even need to submit a PR to the binder, you can just write the code to distinguish between steps 3 and 4 using IConfiguration

Same issue here: null and [] is not the same.
Our ugly workaround is [null].

I agree with @andrewslavin .
Take the following settings file as an example:

{
	"Whitelist": null
}

For me this has the meaning that NO whitelist exists and so everybody is allowed to do something.

{
	"Whitelist": []
}

But this means for me that nobody is allowed to do something,
because there IS a whitelist with no elements in it.


What I want to say is that in this example the meaning of null and [] is the opposite and not the same.


I hope you get my point.
Thanks.

@HaoK Status of this issue?

Nothing to be done here, this is just how the binder works, it ignores null values and doesn't set properties