0

I have a console application that uses the .NET Generic Host for reading settings and for dependeny injection.

How can I configure which library is used for parsing the JSON configuraton files (appsettings.json)? I'd like to use Newtonsoft Json.NET instead of the default (System.Text.Json).

Background: my settings files have BigInteger values that are handled quite well by Newtonsoft, but fail to load with the default.

Note that this is explicitly not about ASP.NET but a plain console application. It is extremely difficult to find something applying to a simple console app use case because everything googleable is about ASP.NET.

The code setting up the host currently looks like this:

Host
.CreateDefaultBuilder()
.ConfigureLogging(...)
.ConfigureServices(...)
.UseConsoleLifetime()
.Build()
.StartAsync();

Somewhere in there surely is the place to hook up the Newtonsoft JSON parser.

(.NET 6)

Heinrich Ulbricht
  • 10,064
  • 4
  • 54
  • 85
  • If `BigInteger` is your only problem it's easy to create a custom `JsonConverter` for it. See [Serialising BigInteger using System.Text.Json](https://stackoverflow.com/q/64788895/3744182) – dbc Aug 24 '22 at 21:06
  • 1
    @dcs I don't think that will work because there is no way to plug in your converter. The JsonConfigurationProvider uses the JsonDocument class with fixed options. See the source code here: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs – Matt Bommicino Aug 24 '22 at 21:13
  • *but fail to load with the default.* -- I don't suppose you could share a [mcve] or traceback for that? `JsonDocument` is perfectly happy to contain numbers of arbitrary magnitude and precision since, unlike Json.NET it never actually attempts to parse them to .NET primitives. Instead it simply provides a view onto the original UTF8 byte sequence. If large integer values are causing values it must be happening later in the pipeline. The code you linked to parses to `JsonDocument` then collects the JSON paths + values in a `Dictionary` so that should work OK. – dbc Aug 24 '22 at 21:42
  • 1
    (Not arguing you shouldn't switch to Newtonsoft BTW, just wondering whether there might be a simpler solution.) – dbc Aug 24 '22 at 21:45
  • @dbc So far I have a `List` that fails to populate and a basic search for problems with `BigInteger` that yielded results. I'll try to come up with a minimal example. Simpler solution welcome. I nevertheless would like to leave the question broader as I might need more features in the future. And it should be easy - right? ^^ – Heinrich Ulbricht Aug 24 '22 at 21:55
  • 1
    @dbc you are totally right, it is converted from a string in a static class called ConfigurationBinder – Matt Bommicino Aug 24 '22 at 23:26

1 Answers1

0

Configuration works the same in a console app as it does in ASP.NET. The defaults may be different but the concepts are the same.

You need to add the newtonsoft json configuration provider and take out the default json configuration provider.

See the extension methods documentation here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.newtonsoftjsonconfigurationextensions?view=dotnet-plat-ext-3.1&viewFallbackFrom=dotnet-plat-ext-6.0

See here for adding and removing configuration providers generally: https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers

Full Console Example:

appsettings.json

{
  "MySection": {
    "MyBigValue": 2928298298298292898298292254353453534435353
  }
}

program.cs

//this requires adding the Microsoft.Extensions.Configuration.NewtonsoftJson package 

using System.Numerics;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.Options;

var host = Host
    .CreateDefaultBuilder()
    .ConfigureAppConfiguration((hostingContext, configuration) =>
    {
        //note this whole section is probably unneeded because conversion is happening in the ConfigurationBinder class 
        //and json conversion has nothing to do with it
        
        //remove old JSON source
        var jsonSources = configuration.Sources.Where(s => s is JsonConfigurationSource).Cast<JsonConfigurationSource>().ToList();
        jsonSources.ForEach(s => configuration.Sources.Remove(s));
        
        //replace them with newtonsoft sources
        jsonSources.ForEach(s =>
        {
            if (File.Exists(s.Path))
                configuration.AddNewtonsoftJsonFile(s.Path);
        });
        
  
        //note this will put the JSON sources after the environment variable sources which is not how it is by default
    })
    
    .ConfigureServices((hostcontext, services) =>
    {
        var mySection = hostcontext.Configuration.GetSection("MySection");
        services.Configure<MySection>(mySection);
    })
    .UseConsoleLifetime()
    .Build();
    
    host.StartAsync();


    var mySection = host.Services.GetRequiredService<IOptions<MySection>>().Value;


    Console.WriteLine(mySection.MyBigValueInt);
    Console.ReadLine();



    
class MySection
{
    
    public string MyBigValue { get; set; }

    //a more complex alternative to using this property is to register a TypeConverter for BigInteger that does the string conversion
    public BigInteger MyBigValueInt => BigInteger.Parse(MyBigValue);
    
}
Matt Bommicino
  • 309
  • 1
  • 5
  • Could you please give me a hint where I would apply your suggestion in my sample code? I came across certain extensions but those seemed to work only in ASP.NET and where used when serializing/deserializing at the API level, not for reading settings. – Heinrich Ulbricht Aug 24 '22 at 21:21
  • Ok, ill add a full example although the newtonsoft converter won't help you. See the comments above. – Matt Bommicino Aug 24 '22 at 23:27
  • I see, you are removing the JSON file from the default handling, adding it back with custom handling. This should work with the appsettings.*.json as well as the path is dynamic, right? I'll give that a try, thank you! – Heinrich Ulbricht Aug 25 '22 at 17:52