So I have this Web API that calls a service which in turn, returns a json string.
The string looks a bit like this:
{
"title": "Test",
"slug": "test",
"collection":{ },
"catalog_only":{ },
"configurator":null
}
I have cut this down considerably to make it easier to see my issue.
When I make my API call, I then use a Factory method to factorize the response which looks something like this:
/// <summary>
/// Used to create a product response from a JToken
/// </summary>
/// <param name="model">The JToken representing the product</param>
/// <returns>A ProductResponseViewModel</returns>
public ProductResponseViewModel Create(JToken model)
{
// Create our response
var response = new ProductResponseViewModel()
{
Title = model["title"].ToString(),
Slug = model["slug"].ToString()
};
// Get our configurator property
var configurator = model["configurator"];
// If the configurator is null
if (configurator == null)
throw new ArgumentNullException("Configurator");
// For each item in our configurator data
foreach (var item in (JObject)configurator["data"])
{
// Get our current option
var option = item.Value["option"].ToString().ToLower();
// Assign our configuration values
if (!response.IsConfigurable) response.IsConfigurable = (option == "configurable");
if (!response.IsDesignable) response.IsDesignable = (option == "designable");
if (!response.HasGraphics) response.HasGraphics = (option == "graphics");
if (!response.HasOptions) response.HasOptions = (option == "options");
if (!response.HasFonts) response.HasFonts = (option == "fonts");
}
// Return our Product response
return response;
}
}
Now, as you can see I am getting my configurator property and then checking it to see if it's null.
The json string shows the configurator as null, but when I put a breakpoint on the check, it actually shows it's value as {}.
My question is how can I get it to show the value (null) as opposed to this bracket response?
You're asking for the JToken associated with the configurator key. There is such a token - it's a null token.
You can check this with:
if (configurator.Type == JTokenType.Null)
So if you want to throw if either there's an explicit null token or configurator hasn't been specified at all, you could use:
if (configurator == null || configurator.Type == JTokenType.Null)
For those who use System.Text.Json in newer .Net versions (e.g. .Net 5):
var jsonDocument = JsonDocument.Parse("{ \"configurator\": null }");
var jsonElement = jsonDocument.RootElement.GetProperty("configurator");
if (jsonElement.ValueKind == JsonValueKind.Null)
{
Console.WriteLine("Property is null.");
}
public class Collection
{
}
public class CatalogOnly
{
}
public class RootObject
{
public string title { get; set; }
public string slug { get; set; }
public Collection collection { get; set; }
public CatalogOnly catalog_only { get; set; }
public object configurator { get; set; }
}
var k = JsonConvert.SerializeObject(new RootObject
{
catalog_only =new CatalogOnly(),
collection = new Collection(),
configurator =null,
slug="Test",
title="Test"
});
var t = JsonConvert.DeserializeObject<RootObject>(k).configurator;
Here configurator is null.
Related
I have a public Dictionary<string, PostRenewalActionJobs> Jobs to store some actions I would like to trigger for specific accounts, the key of this dictionary being the account name.
public class PostRenewalActionJobs
{
public List<AlterDatabaseLinkJob> AlterDataBaseLink { get; set; }
public DatabaseConnectionCheckJob DatabaseConnectionCheck { get; set; }
public UnlockDatabaseAccountJob UnlockDatabaseAccount { get; set; }
public LinuxConnectionCheckJob LinuxConnectionCheck { get; set; }
public WindowsConnectionCheckJob WindowsConnectionCheck { get; set; }
public ReplacePasswordInFileJob ReplacePasswordInFile { get; set; }
}
The properties of PostRenewalActionJobs type (AlterDataBaseLink, DatabaseConnectionCheck, etc) can be defined for a specific account or for all accounts by using * as key in the dictionary:
By using below method I am able to retrieve the jobs for an account (if exists) or the general jobs:
public PostRenewalActionJobs GetJobsForAccount(string accountName)
{
return Jobs.ContainsKey(accountName) ? Jobs[accountName] : Jobs["*"];
}
I would like to have a dynamic way of getting a job from the all accounts object ("*") if the one from the specific account is null.
Something like below but whit out repeating the same code for all job types and also a solution that should work when new job types are introduced.
var dbConCheckJob = GetJobsForAccount("someAccount").AlterDataBaseLink;
if(dbConCheckJob == null || !dbConCheckJob.Any())
{
dbConCheckJob = GetJobsForAccount("*").AlterDataBaseLink
}
I was thinking to use some reflection, but I am not sure how to do it.
You don't need to use reflection. You can already determine whether to get the specific jobs for an account or the generic ones, you could then use a Func to get the job you want:
public TJob GetPostJobForAccount<TJob>(string accountName,
Func<PostRenewalActionJobs, TJob> jobSelector) where TJob : JobBase
{
var genericJobs = Jobs["*"];
var accountJobs = Jobs.ContainsKey(accountName) ? Jobs[accountName] : genericJobs;
// Account might be defined but without any job of the given type
// hence selecting from the defaults if need be
return jobSelector(accountJobs) ?? jobSelector(genericJobs);
}
var bobJob = GetPostJobForAccount("bob", x => x.WindowsConnectionCheck);
var aliceJob = GetPostJobForAccount("alice", x => x.UnlockDatabaseAccount);
I found a way to do it, not sure if there is a better way:
public TJob GetPostJobForAccount<TJob>(string accountName)
{
Type type = typeof(PostRenewalActionJobs);
var accountJobs = Jobs[accountName];
var generalJobs = Jobs["*"];
foreach (var item in type.GetProperties())
{
var itemType = item.PropertyType;
var currentType = typeof(TJob);
if (itemType != currentType)
{
continue;
}
var output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(accountJobs, null);
if (output is null)
{
output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(generalJobs, null);
}
return output;
}
return default;
}
I created a database with JSON columns in mysql.
API defined in Swagger.
Writing JSON to the database works without problems, but when reading, the value of the JSON field is shown as an escaped string and not JSON
Here is part of model:
/// <summary>
/// Gets or Sets Doc
/// </summary>
[DataMember(Name="doc")]
public Dictionary<string, string> Doc { get; set; }
I also tried with string type and Dictionary<string, object> but unsuccessful.
Get method is here:
public virtual IActionResult GetDataById([FromRoute][Required]int? dataId, [FromRoute][Required]string jsonNode)
{
if(_context.Data.Any( a => a.Id == dataId)) {
var dataSingle = _context.Data.SingleOrDefault( data => data.Id == dataId);
return StatusCode(200, dataSingle);
} else {
return StatusCode(404);
}
}
}
And resulting JSON resposne looks like this one:
{
"field1": "value1",
"field2": "value2",
"doc": "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":{\"subkey3-1\":\"value3-1\",\"subkey3-2\":\"value3-2\"}}"
}
but correct JOSN should be linke this one:
{
"field1": "value1",
"field2": "value2",
"doc": {
"key1":"value1",
"key2":"value2",
"key3":{
"subkey3-1":"value3-1",
"subkey3-2":"value3-2"
}
}
}
If I try to return just "Doc" (JSON) field, response JSON is properly formatted.
I tried different serialization/deserialization but unsuccessful.
If I have public Dictionary<string, string> Doc { get; set; } in Model I got error:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HM1VN0F0I0IM", Request id "0HM1VN0F0I0IM:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: The property 'Data.Doc' is of type 'Dictionary<string, string>' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'
and for public string Doc { get; set; } in Model I got "doc" field value as escaped string as I mention above.
What would be the best way to go about this?
A few days later I found a resolution.
You need a one helper method that converts a string to object, exactly JObject if you use Json.NET - Newtonsoft.
public static JObject getJsonOutOfData (dynamic selectedData)
{
var data = JsonConvert.SerializeObject(selectedData);
var jsonData = (JObject)JsonConvert.DeserializeObject<JObject>(data);
if (selectedData.Doc != null) {
JObject doc = JObject.Parse(selectedData.Doc);
JObject docNode = JObject.Parse("{\"doc\":" + doc.ToString() + "}");
var jsonDoc = (JObject)JsonConvert.DeserializeObject<JObject>(docNode.ToString());
jsonData.Property("doc").Remove();
jsonData.Merge(jsonDoc, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
});
}
return jsonData;
}
and call it in the IActionResult method
public virtual IActionResult GetDataById([FromRoute][Required]int? accidentId, [FromRoute][Required]string jsonNode)
{
if (_context.Data.Any( a => a.Id == accidentId)) {
var accidentSingle = _context.Data.SingleOrDefault( accident => accident.Id == accidentId);
var result = getJsonOutOfData(accidentSingle);
return StatusCode(200, result.ToString());
} else {
return StatusCode(404);
}
}
In order to use unescaped JSON in POST/PUT requests, you should have a string type as a property type of JSON filed in the database model, and create an additional "request" model and set JSON property as "Object" type:
Database Model:
[DataMember(Name="doc")]
public string Doc { get; set; }
Request Model:
[DataMember(Name="doc")]
public Object Doc { get; set; }
For example Create model looks like:
public virtual IActionResult CreateData([FromBody]DataCreateRequest body)
{
var accidentObject = new Data() {
ColumnOne = body.ColumnOne,
ColumnTwo = body.ColumnTwo,
ColumnThree = body.ColumnThree,
Doc = (bodyDoc != null) ? body.Doc.ToString() : "{}"
};
_context.Data.Add(accidentObject);
_context.SaveChanges();
return StatusCode(200, getJsonOutOfData(accidentObject));
}
I want to post the following DTO to a .NET Core API:
{
"Name": "Foo",
"Street": "Bar",
"DynamicInfo": {
"MetadataAsString": "23423",
"MetadataAsInt": 2,
"MetadataAsBool": true,
"SomeOtherValues": "blub"
}
}
The class I want to map this in C# looks like this:
public class Foo
{
public string Name { get; set; }
public string Street { get; set; }
public Dictionary<string, object> DynamicInfo { get; set; }
}
You can see that I am using two static properties (Name and Street), but I also want to post some dynamic data.
I expect the dynamic data to be written in a dictionary, but unfortunately this does not work.
The result I am getting in my debugger is a little confusing:
So the values arrive successful, but are in a strange format... I dont know how to even access the values.
How can I convert this to just a normal dictionary, containing objects?
For a solution that would not depend on your use case, I would use 2 objects. 1 for client input and 1 that has validated input. The client input object would contain a Dictionary<string,string>. And the validated input can contain your Dictionary<string,object> if its still something you intend to use.
If you use this approach. During validation of the client input, you could use bool.TryParse(DynamicInfo["MetadataAsBool"], out YourBoolean). Then simply add the YourBoolean to your new Dictionary<string,object> objectDictionary like objectDictionary.Add("BoolMetadata", YourBoolean)
I found a solution. The resulting object (ValueKind=String: 23423) is nothing else than a JSONElement. I did not understand this before.
This JSONElement has an enum that tells me what datatype I have, so I can use this to map my Dictionary of JSONElements to another dictionary of "real" objects.
var newDic = new Dictionary<string, object>();
foreach (var d in obj.DynamicInfo)
{
object obt;
string key = d.Key;
var a = enterprise.BasicInformation.TryGetValue(key, out obt);
if (obt == null) continue;
var doc = (JsonElement)obt;
string myString = null;
bool? myBool = null;
int? myInteger = null;
double? myFloatNumber = null;
if (doc.ValueKind == JsonValueKind.String)
{
myString = doc.ToString();
}
if (doc.ValueKind == JsonValueKind.True)
{
myBool = true;
}
if (doc.ValueKind == JsonValueKind.False)
{
myBool = false;
}
if (doc.ValueKind == JsonValueKind.Number)
{
double floatNumber;
doc.TryGetDouble(out floatNumber);
if ((floatNumber % 1) == 0)
{
myInteger = (int)floatNumber;
}
else
{
myFloatNumber = floatNumber;
}
}
if (myString != null)
{
newDic.Add(key, myString);
}
if (myBool != null)
{
newDic.Add(key, myBool);
}
if (myInteger != null)
{
newDic.Add(key, myInteger);
}
if (myFloatNumber != null)
{
newDic.Add(key, myFloatNumber);
}
}
The code might not be perfect - I will try to optimize it. But it does what it should.
Is there any way how to read polymorphic objects from appsettings.json in a strongly-typed way? Below is a very simplified example of what I need.
I have multiple app components, named Features here. These components are created in runtime by a factory. My design intent is that each component is configured by its separate strongly-typed options. In this example FileSizeCheckerOptions and PersonCheckerOption are instances of these. Each feature can be included multiple times with different option.
But with the existing ASP.NET Core configuration system, I am not able to read polymorphic strongly typed options. If the settings were read by a JSON deserializer, I could use something like this. But this is not the case of appsettings.json, where options are just key-value pairs.
appsettings.json
{
"DynamicConfig":
{
"Features": [
{
"Type": "FileSizeChecker",
"Options": { "MaxFileSize": 1000 }
},
{
"Type": "PersonChecker",
"Options": {
"MinAge": 10,
"MaxAge": 99
}
},
{
"Type": "PersonChecker",
"Options": {
"MinAge": 15,
"MaxAge": 20
}
}
]
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FeaturesOptions>(Configuration.GetSection("DynamicConfig"));
ServiceProvider serviceProvider = services.BuildServiceProvider();
// try to load settings in strongly typed way
var options = serviceProvider.GetRequiredService<IOptions<FeaturesOptions>>().Value;
}
Other definitions
public enum FeatureType
{
FileSizeChecker,
PersonChecker
}
public class FeaturesOptions
{
public FeatureConfig[] Features { get; set; }
}
public class FeatureConfig
{
public FeatureType Type { get; set; }
// cannot read polymorphic object
// public object Options { get; set; }
}
public class FileSizeCheckerOptions
{
public int MaxFileSize { get; set; }
}
public class PersonCheckerOption
{
public int MinAge { get; set; }
public int MaxAge { get; set; }
}
The key to answer this question is to know how the keys are generated. In your case, the key / value pairs will be:
DynamicConfig:Features:0:Type
DynamicConfig:Features:0:Options:MaxFileSize
DynamicConfig:Features:1:Type
DynamicConfig:Features:1:Options:MinAge
DynamicConfig:Features:1:Options:MaxAge
DynamicConfig:Features:2:Type
DynamicConfig:Features:2:Options:MinAge
DynamicConfig:Features:2:Options:MaxAge
Notice how each element of the array is represented by DynamicConfig:Features:{i}.
The second thing to know is that you can map any section of a configuration to an object instance, with the ConfigurationBinder.Bind method:
var conf = new PersonCheckerOption();
Configuration.GetSection($"DynamicConfig:Features:1:Options").Bind(conf);
When we put all this together, we can map your configuration to your data structure:
services.Configure<FeaturesOptions>(opts =>
{
var features = new List<FeatureConfig>();
for (var i = 0; ; i++)
{
// read the section of the nth item of the array
var root = $"DynamicConfig:Features:{i}";
// null value = the item doesn't exist in the array => exit loop
var typeName = Configuration.GetValue<string>($"{root}:Type");
if (typeName == null)
break;
// instantiate the appropriate FeatureConfig
FeatureConfig conf = typeName switch
{
"FileSizeChecker" => new FileSizeCheckerOptions(),
"PersonChecker" => new PersonCheckerOption(),
_ => throw new InvalidOperationException($"Unknown feature type {typeName}"),
};
// bind the config to the instance
Configuration.GetSection($"{root}:Options").Bind(conf);
features.Add(conf);
}
opts.Features = features.ToArray();
});
Note: all options must derive from FeatureConfig for this to work (e.g. public class FileSizeCheckerOptions : FeatureConfig). You could even use reflection to automatically detect all the options inheriting from FeatureConfig, to avoid the switch over the type name.
Note 2: you can also map your configuration to a Dictionary, or a dynamic object if you prefer; see my answer to Bind netcore IConfigurationSection to a dynamic object.
Based on Metoule answer, I've created reusable extension method, that accepts delegate that accepts section and returns instance to bind to.
Please note that not all edge cases are handled (e.g. Features must be list, not array).
public class FeaturesOptions
{
public List<FeatureConfigOptions> Features { get; set; }
}
public abstract class FeatureConfigOptions
{
public string Type { get; set; }
}
public class FileSizeCheckerOptions : FeatureConfigOptions
{
public int MaxFileSize { get; set; }
}
public class PersonCheckerOptions : FeatureConfigOptions
{
public int MinAge { get; set; }
public int MaxAge { get; set; }
}
FeaturesOptions options = new FeaturesOptions();
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("path-to-the-appsettings.json")
.Build();
configuration.Bind(options, (propertyType, section) =>
{
string type = section.GetValue<string>("Type");
switch (type)
{
case "FileSizeChecker": return new FileSizeCheckerOptions();
case "PersonChecker": return new PersonCheckerOptions();
default: throw new InvalidOperationException($"Unknown feature type {type}"); // or you can return null to skip the binding.
};
});
appsettings.json
{
"Features":
[
{
"Type": "FileSizeChecker",
"MaxFileSize": 1000
},
{
"Type": "PersonChecker",
"MinAge": 10,
"MaxAge": 99
},
{
"Type": "PersonChecker",
"MinAge": 15,
"MaxAge": 20
}
]
}
IConfigurationExtensions.cs
using System.Collections;
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// </summary>
/// <param name="requestedType">Abstract type or interface that is about to be bound.</param>
/// <param name="configurationSection">Configuration section to be bound from.</param>
/// <returns>Instance of object to be used for binding, or <c>null</c> if section should not be bound.</returns>
public delegate object? ObjectFactory(Type requestedType, IConfigurationSection configurationSection);
public static class IConfigurationExtensions
{
public static void Bind(this IConfiguration configuration, object instance, ObjectFactory objectFactory)
{
if (configuration is null)
throw new ArgumentNullException(nameof(configuration));
if (instance is null)
throw new ArgumentNullException(nameof(instance));
if (objectFactory is null)
throw new ArgumentNullException(nameof(objectFactory));
// first, bind all bindable instance properties.
configuration.Bind(instance);
// then scan for all interfaces or abstract types
foreach (var property in instance.GetType().GetProperties())
{
var propertyType = property.PropertyType;
if (propertyType.IsPrimitive || propertyType.IsValueType || propertyType.IsEnum || propertyType == typeof(string))
continue;
var propertySection = configuration.GetSection(property.Name);
if (!propertySection.Exists())
continue;
object? propertyValue;
if (propertyType.IsAbstract || propertyType.IsInterface)
{
propertyValue = CreateAndBindValueForAbstractPropertyTypeOrInterface(propertyType, objectFactory, propertySection);
property.SetValue(instance, propertyValue);
}
else
{
propertyValue = property.GetValue(instance);
}
if (propertyValue is null)
continue;
var isGenericList = propertyType.IsAssignableTo(typeof(IList)) && propertyType.IsGenericType;
if (isGenericList)
{
var listItemType = propertyType.GenericTypeArguments[0];
if (listItemType.IsPrimitive || listItemType.IsValueType || listItemType.IsEnum || listItemType == typeof(string))
continue;
if (listItemType.IsAbstract || listItemType.IsInterface)
{
var newListPropertyValue = (IList)Activator.CreateInstance(propertyType)!;
for (int i = 0; ; i++)
{
var listItemSection = propertySection.GetSection(i.ToString());
if (!listItemSection.Exists())
break;
var listItem = CreateAndBindValueForAbstractPropertyTypeOrInterface(listItemType, objectFactory, listItemSection);
if (listItem is not null)
newListPropertyValue.Add(listItem);
}
property.SetValue(instance, newListPropertyValue);
}
else
{
var listPropertyValue = (IList)property.GetValue(instance, null)!;
for (int i = 0; i < listPropertyValue.Count; i++)
{
var listItem = listPropertyValue[i];
if (listItem is not null)
{
var listItemSection = propertySection.GetSection(i.ToString());
listItemSection.Bind(listItem, objectFactory);
}
}
}
}
else
{
propertySection.Bind(propertyValue, objectFactory);
}
}
}
private static object? CreateAndBindValueForAbstractPropertyTypeOrInterface(Type abstractPropertyType, ObjectFactory objectFactory, IConfigurationSection section)
{
if (abstractPropertyType is null)
throw new ArgumentNullException(nameof(abstractPropertyType));
if (objectFactory is null)
throw new ArgumentNullException(nameof(objectFactory));
if (section is null)
throw new ArgumentNullException(nameof(section));
var propertyValue = objectFactory(abstractPropertyType, section);
if (propertyValue is not null)
section.Bind(propertyValue, objectFactory);
return propertyValue;
}
}
}
I am using Stripe.Net to handle the payments. When I start to test the "charge.refund" webhook, I am getting NULL invoice property in code behind but invoice value exists in Stripe webhook event and I confirmed the invoice is exist dashboard too.
Noticed the versions in Stripe.Net and configured webhook API is different.
Dashboard Webhook API version: 2017-08-15
Stripe.Net version: 16.12.0.0 (It support 2018-02-06).
Here is the Stripe webhook event
Here is the breaking code(charge.Invoice.SubscriptionId breaking with Null reference)
Anybody faced this issue before?
Thanks
Looking at the source code for the Stripe.Mapper<T>.MapFromJson
// the ResponseJson on a list method is the entire list (as json) returned from stripe.
// the ObjectJson is so we can store only the json for a single object in the list on that entity for
// logging and/or debugging
public static T MapFromJson(string json, string parentToken = null, StripeResponse stripeResponse = null)
{
var jsonToParse = string.IsNullOrEmpty(parentToken) ? json : JObject.Parse(json).SelectToken(parentToken).ToString();
var result = JsonConvert.DeserializeObject<T>(jsonToParse);
// if necessary, we might need to apply the stripe response to nested properties for StripeList<T>
ApplyStripeResponse(json, stripeResponse, result);
return result;
}
public static T MapFromJson(StripeResponse stripeResponse, string parentToken = null)
{
return MapFromJson(stripeResponse.ResponseJson, parentToken, stripeResponse);
}
private static void ApplyStripeResponse(string json, StripeResponse stripeResponse, object obj)
{
if (stripeResponse == null)
{
return;
}
foreach (var property in obj.GetType().GetRuntimeProperties())
{
if (property.Name == nameof(StripeResponse))
{
property.SetValue(obj, stripeResponse);
}
}
stripeResponse.ObjectJson = json;
}
which is deserializing the JSON using JSON.Net,
the fact that the StripeCharge.Invoice property is marked with [JsonIgnore] attribute.
#region Expandable Invoice
/// <summary>
/// ID of the invoice this charge is for if one exists.
/// </summary>
public string InvoiceId { get; set; }
[JsonIgnore]
public StripeInvoice Invoice { get; set; }
[JsonProperty("invoice")]
internal object InternalInvoice
{
set
{
StringOrObject<StripeInvoice>.Map(value, s => this.InvoiceId = s, o => this.Invoice = o);
}
}
#endregion
and finally how the InternalInvoice property is mapped via the StringOrObject<T>
StringOrObject<StripeInvoice>.Map(value, s => this.InvoiceId = s, o => this.Invoice = o);
You can see in the class definition
internal static class StringOrObject<T>
where T : StripeEntityWithId
{
public static void Map(object value, Action<string> updateId, Action<T> updateObject)
{
if (value is JObject)
{
T item = ((JToken)value).ToObject<T>();
updateId(item.Id);
updateObject(item);
}
else if (value is string)
{
updateId((string)value);
updateObject(null);
}
}
}
That if the value passed is a string, that it will set the Invoice object property to null
else if (value is string)
{
updateId((string)value);
updateObject(null);
}
So the behavior you describe is as designed based on the shown data and code.
You would probably need to extract the InvoiceId and try to retrieve it (the invoice) in order to use its members.