parse values while configuring options

Viewed 602

I'm using .NET Core and want to configure my options. Based on this sample class

public class MySettings
{
    public Dictionary<int, string> Mapping { get; set; }
}

I added a mapping to the appsettings.json file

{
  "MySettings": {
    "Mapping": {
      "9454545": "agf51528gfhdfg",
      "13544": "bfds28745hfghsdfghd"
    }
  }
}

but as you can see the keys are of type string, not integers. It is not possible for me to change the property names to numbers because then I get the error

JSON property names should be strings. Numeric property names are not allowed in JSON

So when calling this in the startup file

IConfigurationSection mySettingsSection = configuration.GetSection("MySettings");
services.Configure<MySettings>(mySettingsSection);

the field Mapping will have 0 items because it is not able to parse the JSON keys to integers automatically. Is there a way I can tell .NET Core to parse those string keys to integers and fill up the dictionary?

3 Answers

Hmm I think you need workaround because cannot allow int at json file. Something like this:

var mySettingsSection = 
configuration.GetSection("MySettings").Get<Dictionary<string, string>>().Select(x => new KeyValuePair<int, string>(int.Parse(x.Key), x.Value)); 

Is there a way I can tell .NET Core to parse those string keys to integers and fill up the dictionary?

Unfortunately, there is no way to achieve that.

Reason

First, internally ConfigurationProvider use a IDictionary<string, string> to hold key-value pair of configuration settings (source code).

/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; }

Second, when JsonConfigurationProvider try to parse JSON file (appsettings.json in your case) and populate that Data property, the parsing result, without doubt, is IDictionary<string, string> type (source code).

public static IDictionary<string, string> Parse(Stream input)
    => new JsonConfigurationFileParser().ParseStream(input);

private IDictionary<string, string> ParseStream(Stream input)
{
    // Exclude implementation
}

Last, when you call services.Configure<MySettings>(mySettingsSection), under the hood it calls Bind method to populate MySettings object by matching property name (string) against configuration key (string) (source code).

/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
public static void Bind(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions)

So, inside .NET Core framework, from building configuration key-value pairs to binding configuration to custom object, the data flow is always around IDictionary<string, string> type. Moreover, there is no custom binding options to let you do any kind of data type conversion.

You have two options, First one is to change the config file to int,string. For example:

{
  "MySettings": {
    "Mapping": {
      1: "a",
      2: "b"
    }
  }
}

Second possibility is to create another properties in MySettings class that will be with correct type (Dictionary<string,string>) and in Mapping field you can do a kind of conversion in the getter.

public class MySettings
{
    public Dictionary<int, string> Mapping => Config.ToDictionary(a => Int32.Parse(a.Key), a => a.Value);

    public Dictionary<int, string> Config { get; set; }
}

If you will call Mapping multiple type you can save the conversion result once and not recalculate it in every call.

Related