I want to load polymorphic data from appsettings. This is a test setup.
async Task Main()
{
var cfg = new
{
PolySection = new PolyCfg
{
Property = new List<IPolyProperty>
{
new PolyImplementationOne { Data = 100 },
new PolyImplementationTwo { Data = 200.5 },
},
JustAnInt = 1000
},
};
var jsonConfig = JsonSerializer.Serialize(cfg);
// Test
var jsonSection = JsonSerializer.Serialize(cfg.PolySection).Dump();
JsonSerializer.Deserialize<PolyCfg>(jsonSection).Dump();
var cfgstrm = new MemoryStream(Encoding.Default.GetBytes(jsonConfig));
var host = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration(config => config.AddJsonStream(cfgstrm))
.ConfigureServices((builder, services) =>
{
services.Configure<PolyCfg>(builder.Configuration.GetSection("PolySection"));
services.AddHostedService<Service>();
})
.Build()
.RunAsync();
}
public class PolyCfg
{
public IList<IPolyProperty> Property { get; set; }
public int JustAnInt { get; set; }
}
[JsonDerivedType(typeof(PolyImplementationOne), nameof(PolyImplementationOne))]
[JsonDerivedType(typeof(PolyImplementationTwo), nameof(PolyImplementationTwo))]
public interface IPolyProperty
{
}
public class PolyImplementationOne: IPolyProperty
{
public int Data { get; set; }
}
public class PolyImplementationTwo: IPolyProperty
{
public double Data { get; set; }
}
public class Service : BackgroundService
{
public Service(IOptions<PolyCfg> opts, ILogger<Service> logger)
{
logger.LogInformation("The int is: {justanint}", opts.Value.JustAnInt); // OK: return 1000
logger.LogInformation("Number of items: {items}", opts.Value.Property.Count); // NOK: return 0
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
The config is serialized correctly:
{"PolySection":{"Property":[{"$type":"PolyImplementationOne","Data":100},{"$type":"PolyImplementationTwo","Data":200.5}],"JustAnInt":1000}}
When I try to deserialize the polysection string itself only, that is also ok, hence the polymorphism is working per se:
However, the IOptions
injected has only the integer in it, the list is not deserialized. How System.text.Json handles polymorphism is a pain on its own - but I can live with that if it is usable at least verywhere. Json.Net handles this polymorphism much better, but I can't figure out how to use that in this scenario.
- Can I make somehow this to work with system.text.json?
- Is there a way to replace serializer in configuration management with Json.Net?
[Update 1]
I have added created custom converters and attached to properties anc types. It seems, that they are also not used when reading the appsetting file.
- Is there any way to inject converters in that deserialisation process?
[Update 2]
- It never will work. According to the source code, the json file is never deserialized, only parsed. And a dictionary is populated while that happens. Lightweight, but restricted functionality.
- There is a provider based on Json.Net: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.newtonsoftjson, there are even extension methods: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.newtonsoftjsonconfigurationextensions.addnewtonsoftjsonfile?view=dotnet-plat-ext-3.1&viewFallbackFrom=net-7.0, however the package itself is marked as deprecated in nuget.