I have a json file that contains nested json which I would like to deserialize into a string property upon configuration binding.
This is the configuration file:
{
"Service": {
"Id": "ccApiMicroservice",
"Configuration": {
"Option1": "value1",
"Option1": "value2"
}
}
}
And this is the corresponding configuration class:
public class ServiceOptions
{
public string Id { get; set; }
[JsonConverter(typeof(JsonStringConverter))]
public string Configuration { get; set; }
}
I have tried to use a custom json converter to convert the nested json to string, however the converter is ignored by the binding mechanism (have breakpoints in the converter's methods, but not one is hit), even though I have configured it in the ConfigureServices like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.Converters.Add(new JsonStringConverter()));
services.Configure<ServiceOptions>(this.Configuration.GetSection("Service"));
}
What am I missing? Why is the converter ignored? Or is there another way how can I deserialize the nested json to string property?
Edit
The reason why I tried to use a custom JSON converter is that if I try to access the ServiceOptions instance anywhere in the application I get the following exception:
System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.'
Here is an example of a possibly easier way for you to get and parse your json items:
JObject config = JObject.Parse(File.ReadAllText("config.json"));
string service_id = (string)config.SelectToken("Service").SelectToken("Id");
Console.WriteLine(service_id);
Related
I have a C# .Net Core Console Application thats supposed to check different Logfiles if these contain Errors and notifies the right person via Email. The Application has an appsetting.json that looks like this:
{
"Programs": [
{
"name": "program 1",
"LoggingPath": "C:\...",
"Emails": [
"person1#company.com",
"person2#company.com",
"person3#company.com"
]
},
{
"name": "program 2",
"LoggingPath": "C:\...",
"Emails": [
"person1#company.com",
"person2#company.com",
"person3#company.com"
]
}
]
}
Now i want to convert this Json into a C# Object List to iterate through the different Programs and do my Log analyzing etc.
I used https://json2csharp.com to convert the Json into this C# Code:
public class Program
{
public string name { get; set; }
public string LoggingPath { get; set; }
public List<string> Emails { get; set; }
}
public class Root
{
public List<Program> Programs { get; set; }
}
My code to initialize the appsettings.json and deserialization looks like this:
public static void Main(string[] args)
{
IConfiguration configuration;
configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
JToken JtokenConfig = Serialize(configuration);
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(JtokenConfig.ToString());
}
But when I try this I get the following Error:
Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[LogAnalyzer.Model+Program]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
I can't change the Json to an array (Without the "Programs":) cause the appsettings.json builder requires an object and not an array.
I have also tried to use this Json to get rid of the array and use a dynamic C# property to deserialize the different object names but the deserialization also failed.
{
"Programs": {
"program 1": {
"LoggingPath": "test",
"Emails": [
"person1#company.com",
"person2#company.com",
"person3#company.com"
]
},
"program 2": {
"LoggingPath": "test",
"Emails": [
"person1#company.com",
"person2#company.com",
"person3#company.com"
]
}
}
}
I read countless stackoverflow threads and other websites but wasnt able to convert the Json into a C# List.
I hope someone here can help me or give me a hint.
you can install Microsoft.Extensions.Configuration.Binder Nuget package and try this code
using Microsoft.Extensions.Configuration;
List<Program> programs = configuration.GetSection("Programs").Get<List<Program>>();
Update
the second json you can deserialize to a dictionary
Dictionary<string,Program> programsDict = configuration.GetSection("Programs")
.Get<Dictionary<string,Program>>();
Suppose we have the following config file
{
"TeamRoster:People:0:Name": "Bryan",
"TeamRoster:People:0:City": "Toronto",
"TeamRoster:People:0:Interests": "Code",
"TeamRoster:People:1:Name": "Sebastian",
"TeamRoster:People:1:City": "Vancouver",
"TeamRoster:People:1:ApartmentType": "Condo"
}
And the following POCOs to represent this config:
public class TeamRoster
{
public Person[] People { get; set; }
}
public class Person
{
public string Name { get; set; }
public string City { get; set; }
}
Assuming I'm using a ConfigurationBuilder to read the dotnet core config with something like:
IConfiguration config =
new ConfigurationBuilder()
.AddJsonFile("file.json")
.Build();
var myConfig = config.GetSection("TeamRoster").Get<TeamRoster>();
How can I capture the additional properties that weren't part of the Person object (Interests, ApartmentType)?
My reasoning is beyond the trivial example above. In my model, I might have dozens of additional attributes (eg like polymorphic types) and I'd want to capture them without having to define them in the config object. Ideally, I'd want to do this like [JsonExtendedData] but as far as I understand, even though the data is expressed as Json that's just the source. It's read into a config object and then bound.
Is there a way to customize the .net core binding?
To shed some light on this, IConfigurationRoot within Microsoft.Extensions.Configuration abstracts the configuration data from its underlying configuration sources. Out of the box, configuration data can come from Json, Azure KeyVault, Command Line, Environment Variables, Xml, Memory, Ini files, User secrets and individual files (key per file).
Each of these configuration sources loads their data into a dictionary where hierarchical values are separated using ':'. When you fetch data from a IConfiguration object, it attempts to resolve the value from each data source in reverse order. Thus the last added configuration source has the highest precedence.
The mapping of IConfiguration objects to POCO objects is done using the ConfigurationBinder. While the ConfigurationBinder does support resolution of values using TypeConverters, this scenario is only helpful for mapping individual string values within the config dictionary, not entire objects.
The ConfigurationBinder has native support for binding collections, so in theory the following two approaches can be used for the following config.
Separate Fixed Values from Dynamic Values
Represent the data so that the dynamic data is separate from the static data.
{
"auth": {
"providers": [
{
"name": "clientsecret",
"loginUrl": "https://login.microsoftonline.com/mytenant/oauth2",
"fields": {
"client_id": "xxx",
"client_secret": "yyy"
"resource": "https://management.azure.com",
"grant_type": "client_credentials"
}
},
...
]
}
}
When binding the dynamic fields to a Dictionary<string,string>, the Binder maps fields as Key/Value pairs. The key advantage for this approach is we're able to leverage TypeConverters and type-safety with the fixed fields.
public class AuthProvider
{
// FIXED FIELDS
public string Name { get; set; }
public Uri LoginUrl { get; set; }
// DYNAMIC FIELDS
public Dictionary<string,string> Fields { get; set; }
}
Map all Data as Dynamic
The slightly more hackish route is to describe all the fields in the data as siblings and then bind the entire data as a Dictionary.
{
"auth": {
"providers": [
{
"name": "clientsecret",
"loginurl": "https://somewhere",
"client_id": "xxx",
"client_secret": "yyy",
"scope": "https://url",
"grant_type": "client_credentials"
}
]
}
}
We can simply take advantage of binding directly to a dictionary. Note we use lose the type-converter for the Uri field:
public class AuthProvider : Dictionary<string,string>
{
public string Name => this["name"]
public string LoginUrl => this["loginUrl"]
public Dictionary<string,string> Fields
{
get
{
var propertyNames = GetType().GetProperties().Select(x => x.Name).ToArray();
var keyPairs = this.Where(kvp => !propertyNames.Contains(kvp.Key));
return
new Dictionary<string,string>(
keyPairs,
StringComparer.OrdinalIgnoreCase);
}
}
}
The following documentation illustrates how to use the Options Pattern in ASP.NET Core to create a strongly-typed options class to access JSON configuration data.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options
This C# class
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
represents a portion of this JSON configuration file (the first two root-level properties)
{
"option1": "value1_from_json",
"option2": -1,
"subOptions": {
"subOption1": "subvalue1_from_json",
"subOption2": 200
}
}
I want to add another C# property named SubOptions to the MyOptions class that returns the raw data of the subOptions JSON sub-section, without creating a strongly-typed class for that sub-section of the JSON configuration file, but I don't know what data type to use (or if it's even possible to do that).
If I use string, I get a runtime error when service.Configure<MyOptions>(Configuration); is called, saying System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.
If I use object or dynamic, I get a different runtime error when service.AddSingleton(cfg => cfg.GetService<IOptions<MyOptions>>().Value); is called to register an instance of the MyOptions class, saying System.ArgumentNullException: 'Value cannot be null. Parameter name: type'
If I use JObject, I get {} back when I access the SubOptions property of the MyOptions object that's injected into my API Controller.
I know I can convert the sub-section to a JSON string property by escaping the sub-section data, but I want to avoid treating the sub-section as a string, and instead leave it as raw JSON.
Is it possible to do what I want to do? Is there a data type that works with the Options Pattern that will allow me to access the JSON sub-section without having to create a strongly-typed class?
*For background, I'm trying to create an API Controller method that returns the content of the JSON sub-section to the API client. I want to avoid using a strongly-typed class for the sub-section, so that the JSON configuration file can be edited on the server, adding new properties and values to the sub-section that will be returned to the API client, without having to update the C# code and redeploy the API service. In other words, I want the JSON sub-section to be 'dynamic', and just pull it and send it to the client. *
You can sorta do get raw configuration object by forcing your SubOptions property to be of IConfigurationSection:
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
public IConfigurationSection SubOptions { get; set; } // returns the "raw" section now
public string SubOptions_take2 { get; set; }
}
so you would still bind your strongly typed object in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration);
...
}
but this is where luck appears to run out, because even though it is a whole section - as far as options binder is concerned it's all been deserialised and parsed into hierarchy of values already. There appears to be no easy way to reassemble it back into one string. Injecting IOptionsMonitor allows you to get the values by opting for .GetChildren() but I could not find an obvious way to get the whole hierarchy without writing custom code to just recursively walk it (which I will leave out for you to play with should you feel this is worth the effort):
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions = _options.SubOptions as ConfigurationSection;
var children = subOptions.GetChildren(); // you see, the config has already been parsed into this hierarchy of items - it's too late to get the raw string value
var s = JsonConvert.SerializeObject(children);
// will produce something like this JSON:
//[{"Path":"SubOptions:subOption1","Key":"subOption1","Value":"subvalue1_from_json"},{"Path":"SubOptions:subOption2","Key":"subOption2","Value":"200"}]
}
one way around it will be to actually encode your json as string in the config file:
"subOptions_take2": "{\"subOption1\": \"subvalue1_from_json\",\"subOption2\": 200}"
then you can just grab it later:
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions_string = _options.SubOptions_take2;// this is valid json now: {"subOption1": "subvalue1_from_json","subOption2": 200}
}
I guess, you can use JObject from Newtonsoft.Json package - it's the default JSON parser & serializer in Asp.Net Core
I'm trying to deserialize an configuration-JSON dynamically to an object.
The JSON is a combination of a base and a custom for each service I want to configure.
one config looks like
{
"_clr": "MyNamespace.Models.Admin.MailServiceConfig, Assembly",
"configuration": {
"mailWarning": 120
}
}
the only thing what changes is the content of "configuration" (another service has another configuration with more properties with more levels)
So the classes for deserializing would look like this:
public class ServiceConfiguration
{
[JsonProperty("_clr")]
public string Clr { get; set; }
[JsonIgnore]
public IServiceValidation Configuration { get; set; }
}
public interface IServiceValidation
{
bool ThrowError();
}
public class ExtraConfiguration<T>: ServiceConfiguration where T: IServiceValidation
{
[JsonProperty("configuration")]
public new T Configuration { get; set; }
}
My target is to deserialize to ServiceConfiguration to get Clr - with this info I would deserialize a second time to ServiceConfiguration<[Clr]>.
I wrote the method for this:
private ServiceConfiguration _getConfiguration(string configuration)
{
ServiceConfiguration config = JsonConvert.DeserializeObject<ServiceConfiguration>(configuration);
string className = config.Clr; // classname must be "Namespace.MyClass, MyAssembly"
Type baseType = typeof(ExtraConfiguration<>);
Type[] typeArgs = {Type.GetType(className)};
Type genericType = baseType.MakeGenericType(typeArgs);
return (ServiceConfiguration)JsonConvert.DeserializeObject(configuration, genericType);
}
This returns the casted object - the problem is, I want to access the property Configuration which is now T as IServiceValidation to execute ThrowError(). But when I try to access, Configuration is null.
When debugging, I can see, there are now two Configuration-Properties. One is null and one has the object I want - any idea how I can access the second one as IServiceValidation?
I would take a different approach.
Fetch the type from the json, fetch the config json from the original json and create the dynamic service validator from it.
var configuration = #"{
""_clr"": ""Playground.MailServiceConfig, Playground"",
""configuration"": {
""mailWarning"": 120
}
}";
var configurationJson = JObject.Parse(configuration);
var type = Type.GetType(configurationJson["_clr"].Value<string>());
var config = configurationJson["configuration"] as JObject;
var serviceValidation = config.ToObject(type) as IServiceValidation;
Is is possible to bind properties from a JSON file (appsettings.json) to a class that uses different property names?
{
"WebTarget": {
"WebURL": "http://www.stackoverflow.com"
}
}
public class MyServiceOptions
{
public string Url { get; set; }
}
I want to take the WebURL setting and map it to the Url property in the options class. I've tried [DataMember] and [JsonProperty] but they don't work.
I know it's not ideal and the property names should match what's in the JSON but this one is a special case.
Yes it is possible. It requires a little more manual configuration
services.Configure<MyServiceOptions>(myOptions => {
myOptions.Url = Configuration.GetSection("WebTarget").GetValue<string>("WebURL", string.Empty);
});
Reference Configure simple options with a delegate