When I want to add a subscriber to a interest, setting the boolean value to true, it remains false. I have no problems updating the merge-fields values using a similar request. When debugging I noticed one difference between the two and that is that the interests have a "key" and "value" in the request while the merge-fields do not:
The response concerning interests looks like this and it doesn't either have the "key" and "value".
What am I doing incorrectly?
The relevant part of my updateMember function looks like this:
static void updateMember(){
Dictionary<string, object> mergefieldsDic = new Dictionary<string, object>();
mergefieldsDic.Add("POSTCODE","4242");
Dictionary<string, bool> interestDic = new Dictionary<object, bool>();
interestDic.Add("cb0b422bf8", true);
request.RequestFormat = DataFormat.Json;
request.AddBody(new MemberRequest(mergefieldsDic, interestDic));
IRestResponse<Member> response = client.Execute<Member>(request);
foreach (var key in response.Data.merge_fields.Keys)
{
string value = response.Data.merge_fields[key].ToString();
Console.WriteLine(value);
}
foreach (var key in response.Data.interests.Keys)
{
string value = response.Data.interests[key].ToString();
Console.WriteLine(value);
}
}
My MemberRequest class looks like this
public class MemberRequest
{
public MemberRequest(){}
public MemberRequest(Dictionary<string, object> merge_fields, Dictionary<object, bool> interests)
{
this.merge_fields = merge_fields;
this.interests = interests;
}
public string email_address { get; set; }
public string status { get; set; }
public Dictionary<string,object> merge_fields { get; set; }
public Dictionary<object, bool> interests { get; set; }
}
And my Member class looks like this
public class Member
{
public string id { get; set; }
public string email_address { get; set; }
public string status { get; set; }
public Dictionary<string,object> merge_fields { get; set; }
public Dictionary<object,bool> interests { get; set; }
}
I've tried to change the both interests dictionaries to <bool,object>,\<string,bool> and <string,string> but nothing worked.
I manage to get to it to work if I don't use the MemberRequest class and just do request.AddBody(new { interests = new { cb0b422bf8 = true} });
For some reason I did not get an error when I updated the member but when I tried to create a new member with the aforementioned classes I got a bad request from the API that the interests were an array and not an object as it has to be. To solve this I changed my interest Dictionary<string,bool>to Dictionary<string,object>.
Initially when I to changed the Dictionary I ended up having two nearly identical constructors and therefore my error was "The call is ambiguous between the following methods or properties".
Related
I have an appSettings.json file with the following group:
"Devices": {
"_850347_74Crompton1": "3605",
"_850532_41Crompton2": "813",
"_850722_18IMEElectricity": "707",
"_850766_85DustNoise1": "306",
"_850772_63Dustnoise2": "2866",
"_850774_29DustNoise3": "3104",
"_863859_63Level": "22601",
"_864233_30": "713",
"_864319_07noise": "606"
}
My Devices class is:
public class Devices
{
public string _850347_74Crompton1 { get; set; }
public string _850532_41Crompton2 { get; set; }
public string _850722_18IMEElectricity { get; set; }
public string _850766_85DustNoise1 { get; set; }
public string _850772_63Dustnoise2 { get; set; }
public string _850774_29DustNoise3 { get; set; }
public string _863859_63Level { get; set; }
public string _864233_30 { get; set; }
public string _864319_07noise { get; set; }
}
I can easily get to the values in this object, using something like:
var myDevice = config.GetRequiredSection("Devices").Get<Devices>();
And refer to a value of my choice, for example:
var myValue = myDevice._850347_74Crompton1;
var anotherValue = myDevice._864233_30;
But I don't want to hardcode it like this. I want to traverse the list (properties), using something like this:
foreach (Devices d in Devices)
{
myDevice = d.Key;
myDeviceValue = d.Value;
}
I just can't see to get it to work. No errors, just getting nulls or not "formed" properly type code.
Thanks.
UPDATE
I've tried this, but I get an error.
Devices iot = config.GetRequiredSection("Devices").Get<Devices>();
foreach (Devices device in iot)
{
...
}
Severity Code Description Project File Line Suppression State
Error CS1579 foreach statement cannot operate on variables of type
'appSettings.Devices' because 'appSettings.Devices' does not contain a
public instance or extension definition for
'GetEnumerator' IoT_CaptisDataCapture .....\Program.cs 60 Active
Read the JSON as Dictionary<string, string>.
var myDevice = config.GetRequiredSection("Devices").Get<Dictionary<string, string>>();
foreach (KeyValuePair<string, string> kvp in myDevice)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
I don't want to use lots of if statements to determine which property needs to be accessed and instead wondered if something like a placeholder could be used.
I attempted to code something with placeholders and this is where I encounted my problem.
if (rootObject.permissions.{commandGroup}.{commandName})
{
//do something
}
This would allow the property accessed to change based on the value of the string in commandGroup and commandName without having to use several if statements for when the JSON gets expanded.
Here is the problem if statement:
//Command is an instance of CommandInfo from Discord.Net
string commandGroup = command.Module.Group;
string commandName = command.Name;
if (rootObject.permissions.commandGroup.commandName)
{
//do something
}
Here is how the JSON file is being stored in a class:
internal class RootObject
{
public Permissions permissions { get; set; }
public int points { get; set; }
}
internal class Permissions
{
public Response response { get; set; }
}
internal class Response
{
public bool ping { get; set; }
public bool helloWorld { get; set; }
}
For example, if the commandGroup was Response and the commandName was ping, how would I use an if statement to determine if the value stored in rootObject.permissions.response.ping.
You could do it using reflection, like this:
public static class PermissionsExtensions {
public static T CommandGroup<T>(this Permissions permissions, string commandGroup)
{
PropertyInfo commandGroupProperty = typeof(Permissions).GetProperty(commandGroup);
return (T)(commandGroupProperty.GetValue( permissions));
}
public static bool CommandProperty<T>(this T commandGroup, string commandProperty)
{
PropertyInfo commandPropertyProperty = typeof(T).GetProperty( commandProperty);
return (bool)(commandPropertyProperty.GetValue( commandGroup));
}
}
Then you would use it like so:
bool result = rootObject.permissions.CommandGroup<Response>( "response").CommandProperty( "ping");
Tip: use capitalized names for properties in classes, and lower-case names for parameters.
It seems like the properties you want to access are all bool. You can store all of them in a Dictionary<string, bool>:
internal class RootObject
{
public Dictionary<string, bool> permissions { get; set; } = new Dictionary<string, bool> {
{ "response.ping", false },
{ "response.helloworld", false },
// add more here...
};
public int points { get; set; }
}
Now you you can access the dictionary like this:
if (rootObject.permissions[$"{commandGroup}.{commandName}"])
I have following Json-based configuration file:
{
"PostProcessing": {
"ValidationHandlerConfiguration": {
"MinimumTrustLevel": 80,
"MinimumMatchingTrustLevel": 75
},
"MatchingCharacterRemovals": [
"-",
"''",
":"
]
},
"Processing": {
"OrderSelection": {
"SelectionDaysInterval": 30,
"SelectionDaysMaximum": 365
}
}
}
As serialization framework I use Newtonsoft. To serialize this config into objects I have implemented following classes:
[JsonObject(MemberSerialization.OptIn)]
public class RecognitionConfiguration {
[JsonProperty(PropertyName = "PostProcessing", Required = Required.Always)]
public PostRecognitionConfiguration PostRecognitionConfiguration { get; set; }
[JsonProperty(PropertyName = "Processing", Required = Required.Always)]
public ProcessRecognitionConfiguration ProcessRecognitionConfiguration { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class PostRecognitionConfiguration {
[JsonProperty(Required = Required.Always)]
public ValidationHandlerConfiguration ValidationHandlerConfiguration { get; set; }
[JsonProperty] public List<string> MatchingCharacterRemovals { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class ProcessRecognitionConfiguration {
[JsonProperty(PropertyName = "OrderSelection", Required = Required.Always)]
public OrderSelectionConfiguration OrderSelectionConfiguration { get; set; }
}
In a class I try to serialize a specific configuration section into these class structures using IConfigurationSection.Get().
var serializedConfiguration = this.ConfigurationSection.Get<RecognitionConfiguration>();
But when I debug the code, I always get an "empty" variable serializedConfiguration which is not null, but all properties are null.
If I use
this.ConfigurationSection.GetSection("Processing").Get<ProcessRecognitionConfiguration>()
or change the naming of the properties in the json file to exactly match the property names in the classes like this:
{
"ProcessRecognitionConfiguration": {
"OrderSelectionConfiguration": {
"SelectionDaysInterval": 30,
"SelectionDaysMaximum": 365
}
}
}
it it works fine. Do you have any idea, why setting PropertyName on JsonProperty does not seem to have any effect?
That is by design. Binding to POCO via configuration is done by convention. Not like Model Binding to Controller Action parameters.
It matches property names on the POCO to keys in the provided JSON.
Reference Configuration in ASP.NET Core
So either you change the settings to match the class like you showed in the original question, or change the class to match the settings keys in the Json-based configuration file.
[JsonObject(MemberSerialization.OptIn)]
public class RecognitionConfiguration {
[JsonProperty(PropertyName = "PostProcessing", Required = Required.Always)]
public PostRecognitionConfiguration PostProcessing{ get; set; }
[JsonProperty(PropertyName = "Processing", Required = Required.Always)]
public ProcessRecognitionConfiguration Processing{ get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class PostRecognitionConfiguration {
[JsonProperty(Required = Required.Always)]
public ValidationHandlerConfiguration ValidationHandlerConfiguration { get; set; }
[JsonProperty]
public List<string> MatchingCharacterRemovals { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class ProcessRecognitionConfiguration {
[JsonProperty(PropertyName = "OrderSelection", Required = Required.Always)]
public OrderSelectionConfiguration OrderSelection { get; set; }
}
public partial class ValidationHandlerConfiguration {
[JsonProperty("MinimumTrustLevel")]
public long MinimumTrustLevel { get; set; }
[JsonProperty("MinimumMatchingTrustLevel")]
public long MinimumMatchingTrustLevel { get; set; }
}
public partial class OrderSelectionConfiguration {
[JsonProperty("SelectionDaysInterval")]
public long SelectionDaysInterval { get; set; }
[JsonProperty("SelectionDaysMaximum")]
public long SelectionDaysMaximum { get; set; }
}
EDIT: I found this one is much more pleasant than my previous solutions: Bind everything in an ExpandoObject, write them to JSON and use JSON.NET to bind them back. Using the code of this article:
namespace Microsoft.Extensions.Configuration
{
public static class ConfigurationBinder
{
public static void BindJsonNet(this IConfiguration config, object instance)
{
var obj = BindToExpandoObject(config);
var jsonText = JsonConvert.SerializeObject(obj);
JsonConvert.PopulateObject(jsonText, instance);
}
private static ExpandoObject BindToExpandoObject(IConfiguration config)
{
var result = new ExpandoObject();
// retrieve all keys from your settings
var configs = config.AsEnumerable();
foreach (var kvp in configs)
{
var parent = result as IDictionary<string, object>;
var path = kvp.Key.Split(':');
// create or retrieve the hierarchy (keep last path item for later)
var i = 0;
for (i = 0; i < path.Length - 1; i++)
{
if (!parent.ContainsKey(path[i]))
{
parent.Add(path[i], new ExpandoObject());
}
parent = parent[path[i]] as IDictionary<string, object>;
}
if (kvp.Value == null)
continue;
// add the value to the parent
// note: in case of an array, key will be an integer and will be dealt with later
var key = path[i];
parent.Add(key, kvp.Value);
}
// at this stage, all arrays are seen as dictionaries with integer keys
ReplaceWithArray(null, null, result);
return result;
}
private static void ReplaceWithArray(ExpandoObject parent, string key, ExpandoObject input)
{
if (input == null)
return;
var dict = input as IDictionary<string, object>;
var keys = dict.Keys.ToArray();
// it's an array if all keys are integers
if (keys.All(k => int.TryParse(k, out var dummy)))
{
var array = new object[keys.Length];
foreach (var kvp in dict)
{
array[int.Parse(kvp.Key)] = kvp.Value;
}
var parentDict = parent as IDictionary<string, object>;
parentDict.Remove(key);
parentDict.Add(key, array);
}
else
{
foreach (var childKey in dict.Keys.ToList())
{
ReplaceWithArray(input, childKey, dict[childKey] as ExpandoObject);
}
}
}
}
}
Usage:
var settings = new MySettings();
this.Configuration.BindJsonNet(settings);
Here is my testing MySettings class:
public class MySettings
{
[JsonProperty("PostProcessing")]
public SomeNameElseSettings SomenameElse { get; set; }
public class SomeNameElseSettings
{
[JsonProperty("ValidationHandlerConfiguration")]
public ValidationHandlerConfigurationSettings WhateverNameYouWant { get; set; }
public class ValidationHandlerConfigurationSettings
{
[JsonProperty("MinimumTrustLevel")]
public int MinimumTrustLevelFoo { get; set; }
[JsonProperty("MinimumMatchingTrustLevel")]
public int MinimumMatchingTrustLevelBar { get; set; }
}
}
}
After the calling, I get everything as you desired:
Old Answer:
According to the source code here, it is simply (near) impossible to do what you are requiring. I have tried both JsonProperty and DataContract, none of which are honored by the Binder, simply because the source code itself simply use the property name.
If you still insist, there are 2 possibilities, however I do not recommend any as changing properties' names are much simpler:
Fork your source code there, or simply copy that file (in my attempt to trace the code, I rename all methods to something like Bind2, BindInstance2 etc), and rewrite the code accordingly.
This one is very specific to current implementation, so it's not future-proof: the current code is calling config.GetSection(property.Name), so you can write your own IConfiguration and provide your own name for GetSection method and tap it into the bootstrap process instead of using the default one.
Changing PropertyName on JsonProperty does have effect. Here is the same I tried and it did worked for me:
my JSON data:
{"name": "John","age": 30,"cars": [ "Ford", "BMW", "Fiat" ]}
and the Model:
public class RootObject
{
[JsonProperty(PropertyName ="name")]
public string Apple { get; set; }
public int age { get; set; }
public List<string> cars { get; set; }
}
and here is the code:
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
and this is the output i get
You need to set the PropertyName in JsonProperty same as json file property name but your C# model property can be what you wanted, just that they need to be decorated with [JsonProperty(PropertyName ="jsonPropertyName")] Hope this helps you solve your issue.
Happy coding...
I have a class MyClass. I would like to convert this to a dynamic object so I can add a property.
This is what I had hoped for:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
I get the error:
WEB.Models.MyClass does not contain a definition for 'newProperty'
Is that not possible?
The following has worked for me in the past:
It allows you to convert any object to an Expando object.
public static dynamic ToDynamic<T>(this T obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var currentValue = propertyInfo.GetValue(obj);
expando.Add(propertyInfo.Name, currentValue);
}
return expando as ExpandoObject;
}
Based on: http://geekswithblogs.net/Nettuce/archive/2012/06/02/convert-dynamic-to-type-and-convert-type-to-dynamic.aspx
As my object has JSON specific naming, I came up with this as an alternative:
public static dynamic ToDynamic(this object obj)
{
var json = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject(json, typeof(ExpandoObject));
}
For me the results worked great:
Model:
public partial class Settings
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("runTime")]
public TimeSpan RunTime { get; set; }
[JsonProperty("retryInterval")]
public TimeSpan RetryInterval { get; set; }
[JsonProperty("retryCutoffTime")]
public TimeSpan RetryCutoffTime { get; set; }
[JsonProperty("cjisUrl")]
public string CjisUrl { get; set; }
[JsonProperty("cjisUserName")]
public string CjisUserName { get; set; }
[JsonIgnore]
public string CjisPassword { get; set; }
[JsonProperty("importDirectory")]
public string ImportDirectory { get; set; }
[JsonProperty("exportDirectory")]
public string ExportDirectory { get; set; }
[JsonProperty("exportFilename")]
public string ExportFilename { get; set; }
[JsonProperty("jMShareDirectory")]
public string JMShareDirectory { get; set; }
[JsonIgnore]
public string Database { get; set; }
}
I used it in this manner:
private static dynamic DynamicSettings(Settings settings)
{
var settingsDyn = settings.ToDynamic();
if (settingsDyn == null)
return settings;
settingsDyn.guid = Guid.NewGuid();
return settingsDyn;
}
And received this as a result:
{
"id": 1,
"runTime": "07:00:00",
"retryInterval": "00:05:00",
"retryCutoffTime": "09:00:00",
"cjisUrl": "xxxxxx",
"cjisUserName": "xxxxx",
"importDirectory": "import",
"exportDirectory": "output",
"exportFilename": "xxxx.xml",
"jMShareDirectory": "xxxxxxxx",
"guid": "210d936e-4b93-43dc-9866-4bbad4abd7e7"
}
I don't know about speed, I mean it is serializing and deserializing, but for my use it has been great. A lot of flexability like hiding properties with JsonIgnore.
Note: xxxxx above is redaction. :)
You cannot add members to class instances on the fly.
But you can use ExpandoObject. Use factory to create new one and initialize it with properties which you have in MyClass:
public static ExpandoObject Create(int id)
{
dynamic obj = new ExpandoObject();
obj.Id = id;
obj.CreatedAt = DateTime.Now;
// etc
return obj;
}
Then you can add new members:
dynamic dto = Factory.Create(id);
dto.newProperty = "123";
You can't add properties to types at runtime. However, there is an exception which is: ExpandoObject. So if you need to add properties at runtime you should use ExpandoObject, other types don't support this.
Just to add up my experience, if you are using JSON.NET, then below might be one of the solution:
var obj....//let obj any object
ExpandoObject expandoObject= JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
Not tested performances etc.. but works.
Suppose I have this JSON:
{
"Success": true,
"Records": [
{
"f_EMail": "test#me.com",
"f_FirstName": "firstname",
"f_LastName": "lastname",
"f_Country": null
},
{
"f_EMail": "test2#me.com",
"f_FirstName": "firstname2",
"f_LastName": "lastname2",
"f_Country": null
}
]
}
My class looks like this:
public class Result
{
public bool Success { get; set; }
public IEnumrable<Dictionary<string, string>> Records { get; set; }
}
Everything works as expected. But I would like to write my class a little bit differently and put the values inside Record.Data as shown below. I need to find a way to read and write to this model because some values are well known and I would like to access them more directly.
public class Result
{
public bool Success { get; set; }
public IEnumerable<Record> Records { get; set; }
}
public class Record
{
public Dictionary<string, string> Data { get; set; }
public string Email
{
get
{
return Data[KnownRecordField.Email];
}
}
public string FirstName
{
get
{
return Data[KnownRecordField.FirstName];
}
}
...
}
How can I do that?
If you are willing to declare your dictionary as Dictionary<string, object> instead of Dictionary<string, string> you can take advantage of Json.Net's "Extension Data" feature to handle this.
Mark the dictionary with an [JsonExtensionData] attribute.
Make properties for all of the well-known values and give them [JsonProperty] attributes corresponding to their JSON property names.
The well-known JSON properties will be deserialized into their respective members on the class, while all of the remaining values will go into the dictionary.
public class Record
{
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; }
[JsonProperty("f_EMail")]
public string Email { get; set; }
[JsonProperty("f_FirstName")]
public string FirstName { get; set; }
...
}
Fiddle: https://dotnetfiddle.net/hGZ1V7
If you want all the values to go into the dictionary (not just the unknown ones), you can still use the [JsonExtensionData] attribute, then use separate properties to read and write the well-known values directly from the dictionary. Be sure to mark the properties with [JsonIgnore] to avoid potential conflicts during serialization.
public class Record
{
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; }
[JsonIgnore]
public string Email
{
get { return GetDataValue(KnownRecordField.Email); }
set { Data[KnownRecordField.Email] = value; }
}
[JsonIgnore]
public string FirstName
{
get { return GetDataValue(KnownRecordField.FirstName); }
set { Data[KnownRecordField.FirstName] = value; }
}
// use this method to avoid an exception if the well-known value
// isn't present in the dictionary
private string GetDataValue(string key)
{
object value;
return Data.TryGetValue(key, out value) && value != null ? value.ToString() : null;
}
}
public static class KnownRecordField
{
public static readonly string Email = "f_EMail";
public static readonly string FirstName = "f_FirstName";
}
Fiddle: https://dotnetfiddle.net/I04oMM
you can add JsonProperty attribute in record class.
public class Record
{
public Dictionary<string, string> Data { get; set; }
[JsonProperty(PropertyName = "f_EMail")]
public string Email
{
get;set;
}
...
}