Is it possible to enhance the swagger output when you are using dictionary values as either the response or a property on the type?
I am using Swashbuckle Annotations with example SchemaFilter types
Dictionary as a response example
[SwaggerResponse(StatusCodes.Status400BadRequest, "A dictionary containing the property validation errors", typeof(Dictionary<string, string>), contentTypes: "application/json")]
Dictionary as a property on a type example
public class TypeToReturn
{
[SwaggerSchema(Description = "Dictionary<string, object>")]
public IDictionary<string, object> PropertyValues { get; }
}
I can put a more descriptive string in the description field but I would like the outputted example to be better than below as for the property example, it is a dictionary of strings to objects and not strings to strings which the swagger assumes.
{
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
I've read that you can use additionalProperties in the swagger definition but can't see a way to provide that information in Swashbuckle.
Related
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);
}
}
}
I am trying to retrieve JSON data from an API, but one of the property names comes as #data.context . It is not a nested property. I've tested it with ExpandoObject as well, it is exactly like that. Normally in C # I would make a data model like
public class Data
{
public string #odata.context { get ; set; }
}
But this doesn't work, as C# doesn't let me have a comma in the variable name, nor have quotes around it. The # sign is already there
The JSON is as follows: this property that contains a link and then another one that contains a list of objects.
{
"#odata.context": "some link here",
"list" [ {}, {}
]
}
The list of objects do not give me any trouble, only the first property.
You can use JsonPropertyName attribute to map the json to the property e.g:
[JsonPropertyName("#odata.context")]
public string DataContext { get ; set; }
Microsoft docs
You might be consuming a poorly designed API. There are API specifications that tells how to structure and name JSON keys.
One way you can accomplish is
1 Fetch JSON response from API
2 Replace "#data.context" with "Context" or something similar in JSON string
3 Create class with property
public class Data
{
public string Context { get ; set; }
}
4 Deserialise it
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);
I tried to read all the Request and response using Fiddler but in some cases I am getting the request data as JSON, and I want to parse that JSON into a key value pair.
I have tried like this
string requestPost = Utilities.UrlDecode(oSession.GetRequestBodyAsString());
Dictionary<string, string> jsonRequest = new Dictionary<string, string>();
jsonRequest = JsonConvert.DeserializeObject<Dictionary<string, string>>(requestPost);
dataGridViewFiddler.DataSource = jsonRequest;
But this didn't work for me.
this is my Json format
{"entries":[{"http_response_code":200,"network_changed":false,"protocol":"QUIC","request_age_ms":112425,"request_elapsed_ms":27,"sample_rate":0.05,"server_ip":"216.58.197.78:443","status":"ok","url":"https://www.google-analytics.com/","was_proxied":false}],"reporter":"chrome"}
Now if i use dynamic to parse this json its working but
You indicate that you can receive any JSON. However, this will result in severe challenges, because you still have to parse your data to use it in some structured way. Therefore you should make sure you always get a similar input. I will give you to options that are best suited.
Option 1: De-serializing JSON to a Dictionary only works if your data follows the key/value layout. So the layout should always be like this:
{
"key1": "value1",
"key2": "value2"
}
Of course, your value can also be a list. However, the basic layout is still the same. For example:
{
"key1": ["value1", "value2"],
"key2": ["value3", "value4"]
}
This you can just deserialize with your code:
var dict = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(requestPost);
Option 2: if your JSON data is structured with custom data names, you will have to create corresponding models. A good tool that can put you on the right track for this is json2csharp. Let me give an example. If this is your JSON:
[{
"Key": "MyKey",
"Values": [{
"First": "RandomValue",
"Second": "RandomValue"
}, {
"First": "RandomValue",
"Second": "RandomValue"
}]
},
{
"Key": "MyKey",
"Values": [{
"First": "RandomValue",
"Second": "RandomValue"
}, {
"First": "RandomValue",
"Second": "RandomValue"
}]
}]
Then your corresponding models should be:
public class Value
{
public string First { get; set; }
public string Second { get; set; }
}
public class RootObject
{
public string Key { get; set; }
public List<Value> Values { get; set; }
}
And you can deserialize it like this:
var values = JsonConvert.DeserializeObject<List<RootObject>>(json);
Concluding, you can either make sure your JSON data is structured as a Dictionary with key/value pairs. In that case you can directly deserialize to a Dictionary. Otherwise you will have to create custom models to fit your JSON data. Use whatever works best for you! Please note that it is possible to just deserialize random data, but it will make it very hard to make sure your code can handle the data! See Luke's answer on how to do this.
If you want to be able to deserialize any JSON, then the type that you need to deserialize to is simply dynamic, not Dictionary<string, dynamic> because this would mean that you would have to have a string key in your JSON...
Therefore, to deserialize any JSON object, as requested, you need to use the code:
var json = JsonConvert.DeserializeObject(requestPost);
Bearing in mind that JSON can be as simple as:
["bob", "fred"]
Which would not match your model, as there is no key (string in your Dictionary).
I have this JSON (actually Swagger) :
{
"swagger": "2.0",
"info": {
"version": "2.0",
"name": "TheName",
"description": "No description"
}
"somethingElse" : "xyz"
}
I am using Newtonsoft.JSON. I defined my object:
public class WebService
{
public string swagger;
public Dictionary<string, object> AllOthers;
}
As you can see I have not defined "info" or "somethingElse" objects as members. Rather, I want this to get placed into the generic AllOthers dictionary
When I call
var ws = Newtonsoft.Json.JsonConvert.DeserializeObject<WebService>(swaggerJSON);
ws will now correctly contain the swagger version number string, but AllOthers will contain null. I want AllOthers to contain a entires for name and description, and the payloads to by stored in a generic object.
How is this achieved, or can it not be easily?
Thanks
There is an attribute for this [JsonExtensionData]. Mark your dictionary with this attribute:
public class WebService
{
public string swagger;
[JsonExtensionData]
public Dictionary<string, object> AllOthers;
}
var ws = Newtonsoft.Json.JsonConvert.DeserializeObject<WebService>(json);
I hope this helps! cheers.