I have a class Properties and in that I have defined a dictionary like this :
public class Properties
{
public IDictionary<string, string> ExtendedProperties
{
get;
set;
}
}
In the dictionary, there will be 3 keys always present say Name,Number and Age with the option of adding more KeyValuePairs during runtime.
I want to have the 3 keys described above to be present in the dictionary by default while initializing it in my code so that I can use it directly like this:
Properties objProps = new Properties();
objProps.ExtendedProperties["Name"] = "SomeName";
I know I can achieve this in my code by adding the KeyValuePair to the dictionary but I was hoping to have it setup directly in the class using get-set to include the 3 keys. I could not find any solution that does that in the class itself. I looked into this Creating dictionaries with predefined keys but did not find it satisfactory.
how can I achieve this?
Since C# 6 you can do something like:
using System;
using System.Collections.Generic;
public class Properties
{
public IDictionary<string, string> ExtendedProperties { get; set; }
public Properties(string name, string number, string age)
{
this.ExtendedProperties = new Dictionary<string, string>()
{
["Name"] = name,
["Number"] = number,
["Age"] = age
};
}
}
As you can see you need to define it in the constructor.
There is also some cool function you might want to use:
public int this[int param]
{
get { return array[param]; }
set { array[param] = value; }
}
Documentation
If you add something like this, you can do new Properties()["Name"]
Example with your code:
using System;
using System.Collections.Generic;
public class Properties
{
private IDictionary<string, string> extendedProperties;
public string this[string key]
{
get { return extendedProperties[key]; }
set { extendedProperties[key] = value; }
}
public Properties()
{
this.extendedProperties = new Dictionary<string, string>()
{
["Name"] = "something",
["Number"] = "something",
["Age"] = "something"
};
}
}
Like this:
public class Properties
{
public IDictionary<string, string> ExtendedProperties
{
get;
set;
}
public Properties()
{
this.ExtendedProperties = new Dictionary<string, string>()
{
{ "Name", String.Empty },
{ "Number", String.Empty },
{ "Age", String.Empty },
};
}
}
You might want to have a look at some documentation: https://msdn.microsoft.com/en-us/library/bb531208.aspx
How about adding the 3 entries in the constructor?
using System;
using System.Collections.Generic;
namespace My.Namespace
{
public class Properties
{
public IDictionary<string, string> ExtendedProperties { get; set; }
public Properties()
{
ExtendedProperties = new Dictionary<string, string>
{
["Name"] = String.Empty,
["Number"] = String.Empty,
["Age"] = String.Empty
};
}
}
}
You can do this.
public class Properties
{
public IDictionary<string, string> ExtendedProperties
{
get;
set;
}
public Properties(string [] fields)
{
ExtendedProperties = new Dictionary<string, string> ();
foreach(var s in fields)
{
ExtendedProperties.Add(s,string.Empty);
}
}
}
Usage :
Properties p = new Properties(new [] {"Name","Number", "Age"});
Working fiddler code
I would go with implementing IDictionary<string, string> because it is more safe and easier to extend with other keys: (long class to follow)
class Properties : IDictionary<string, string>
{
private Dictionary<string, string> _staticProps;
private Dictionary<string, string> _otherProps;
public Properties()
{
_staticProps = new Dictionary<string, string>
{
{"Name", "" },
{"Number", "" },
{"Age", "" }
};
_otherProps = new Dictionary<string, string>();
}
public ICollection<string> Keys
{
get
{
return (ICollection<String>)_otherProps.Keys.Concat(_staticProps.Keys);
}
}
public ICollection<string> Values
{
get
{
return (ICollection<String>)_otherProps.Values.Concat(_staticProps.Values);
}
}
public int Count
{
get
{
return _otherProps.Count + _staticProps.Count;
}
}
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
public string this[string key]
{
get
{
if (_otherProps.ContainsKey(key))
{
return _otherProps[key];
}
if(_staticProps.ContainsKey(key))
{
return _staticProps[key];
}
throw new KeyNotFoundException(key);
}
set
{
if (_otherProps.ContainsKey(key) || _staticProps.ContainsKey(key))
{
throw new ArgumentException("key exists: " + key);
}
_otherProps[key] = value;
}
}
public bool ContainsKey(string key)
{
return _otherProps.ContainsKey(key) || _staticProps.ContainsKey(key);
}
public void Add(string key, string value)
{
_otherProps.Add(key, value);
}
public bool Remove(string key)
{
if (_staticProps.ContainsKey(key))
{
throw new ArgumentException("key is static, cannot be removed: " + key);
}
return _otherProps.Remove(key);
}
public bool TryGetValue(string key, out string value)
{
return _otherProps.TryGetValue(key, out value) || _staticProps.TryGetValue(key, out value);
}
public void Add(KeyValuePair<string, string> item)
{
if (_staticProps.ContainsKey(item.Key))
{
throw new ArgumentException("key exist an is static: " + item.Key);
}
_otherProps.Add(item.Key, item.Value);
}
public void Clear()
{
_otherProps.Clear();
foreach (var key in _staticProps.Keys)
{
_staticProps[key] = string.Empty;
}
}
public bool Contains(KeyValuePair<string, string> item)
{
return _otherProps.Contains(item) || _staticProps.Contains(item);
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
// define yourself how you want to handle arrayIndex between the two dictionaries
}
public bool Remove(KeyValuePair<string, string> item)
{
if (_staticProps.ContainsKey(item.Key))
{
throw new ArgumentException("key is static, cannot be removed: " + item.Key);
}
return _otherProps.Remove(item.Key);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _otherProps.Concat(_staticProps).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _otherProps.Concat(_staticProps).GetEnumerator();
}
}
I would encapsulate this logic in property itself:
public class Properties
{
private IDictionary<string, string> _extendedProperties;
public IDictionary<string, string> ExtendedProperties
{
get
{
return
_extendedProperties == null ?
new Dictionary<string, string>() { { "Name", "" }, { "Number", "" }, { "Age", "" } } :
_extendedProperties;
}
set
{
_extendedProperties = value;
//here you can also check if value misses those key to add them to _extendedProperties
}
}
}
Related
This question already has answers here:
Serialize/Deserialize dynamic property name using JSON.NET
(4 answers)
Closed 2 years ago.
I need to have a dynamic property-name for the serialization.
public class Home
{
public virtual int Id { get; set; } // value: 2
public virtual string propertyName { get; set; } // value: administration
public virtual string Text { get; set; } // value: text1
}
should serialize to:
{
"Id": 2,
"administration": "text1"
}
Is there any way to serialize that? Which is the best way to deserialize it?
According to this post on how to Dynamically rename or ignore properties without changing the serialized class by Rico Suter, you can add a class which extends DefaultContractResolver named PropertyRenameAndIgnoreSerializerContractResolver.
So the model would look like:
public class Home
{
[JsonProperty("firstName")]
public int Id { get; set; } // value: 2
//public Dictionary<string,string> dictionary { get; set; }
[JsonProperty("propertyName")]
public string propertyName { get; set; } // value: administration
[JsonIgnore]
public string Text { get; set; } // value: text1
}
And serialization would look like this:
var home = new Home();
home.Id = 2;
home.propertyName = "text1";
var jsonResolver = new PropertyRenameAndIgnoreSerializerContractResolver();
jsonResolver.RenameProperty(typeof(Home), "propertyName", "administration");
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = jsonResolver;
var json = JsonConvert.SerializeObject(home, serializerSettings);
Which give the desire output.
Add this class PropertyRenameAndIgnoreSerializerContractResolver.cs:
public class PropertyRenameAndIgnoreSerializerContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _ignores;
private readonly Dictionary<Type, Dictionary<string, string>> _renames;
public PropertyRenameAndIgnoreSerializerContractResolver()
{
_ignores = new Dictionary<Type, HashSet<string>>();
_renames = new Dictionary<Type, Dictionary<string, string>>();
}
public void IgnoreProperty(Type type, params string[] jsonPropertyNames)
{
if (!_ignores.ContainsKey(type))
_ignores[type] = new HashSet<string>();
foreach (var prop in jsonPropertyNames)
_ignores[type].Add(prop);
}
public void RenameProperty(Type type, string propertyName, string newJsonPropertyName)
{
if (!_renames.ContainsKey(type))
_renames[type] = new Dictionary<string, string>();
_renames[type][propertyName] = newJsonPropertyName;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsIgnored(property.DeclaringType, property.PropertyName))
property.ShouldSerialize = i => false;
if (IsRenamed(property.DeclaringType, property.PropertyName, out var newJsonPropertyName))
property.PropertyName = newJsonPropertyName;
return property;
}
private bool IsIgnored(Type type, string jsonPropertyName)
{
if (!_ignores.ContainsKey(type))
return false;
return _ignores[type].Contains(jsonPropertyName);
}
private bool IsRenamed(Type type, string jsonPropertyName, out string newJsonPropertyName)
{
Dictionary<string, string> renames;
if (!_renames.TryGetValue(type, out renames) || !renames.TryGetValue(jsonPropertyName, out newJsonPropertyName))
{
newJsonPropertyName = null;
return false;
}
return true;
}
}
Add a ToJObject method that returns a JObject.
public JObject ToJObject()
{
JObject jObject = new JObject()
{
{ "Id", Id },
{ propertyName, Text }
}
return jObject;
}
Then for Deserializing i would probably create a factory method something like this:
public static Home CreateFromJObject(JObject obj)
{
Home h = new Home();
foreach (var a in obj)
{
if (a.Key == "ID")
{
h.Id = a.Value.Value<int>();
}
else
{
h.propertyName = a.Key;
h.Text = a.Value.Value<string>();
}
}
return h;
}
Ofcause if you have multiple other values in there i would either change it to a switch or make sure that only the needed JObject is passed in there.
Here is the xml
<?xml version="1.0"?>
<TransactionLog>
<RuleViolations>
<error>
<message>error1</message>
<keys>
<key1>val1</key1>
<key2>val2</key2>
<key3>val3</key3>
<key4>val4</key4>
</keys>
</error>
<error>
<message>error1</message>
<keys>
<key1>val5</key1>
<key2>val6</key2>
</keys>
</error>
<error>
<message>error3</message>
<keys>
<key2>val7</key2>
<key3>val8</key3>
<key4>val9</key4>
</keys>
</error>
</RuleViolations>
</TransactionLog>
What I have now:
[XmlRoot("TransactionLog")]
public class TransactionLogModel
{
[XmlArray("RuleViolations")]
[XmlArrayItem("error")]
public List<KeyValuePair<string,string>> RuleViolations { get; set; }
}
But how can we serialize the <keys> section?
The closest SO post I can find is here: Deserialize XML into Dictionary
But I am not using XDocument.
var x = new XmlSerializer(typeof(TransactionLogModel));
var model = (TransactionLogModel)x.Deserialize(new StringReader(log));
How can we deserialize this xml in XmlSerializer?
Firstly, your data model doesn't match your XML -- there are several intermediate classes missing between TransactionLog and keys. Instead, it should look something like:
[XmlRoot("TransactionLog")]
public class TransactionLogModel
{
[XmlElement("RuleViolations")]
public List<RuleViolation> RuleViolations { get; set; }
}
public class RuleViolation
{
public RuleViolation() { this.Errors = new List<Error>(); }
[XmlElement("error")]
public List<Error> Errors { get; set; }
}
public class Error
{
[XmlElement("message")]
public string Message { get; set; }
// To be done.
public List<KeyValuePair<string, string>> Keys { get; set; }
}
Next, to serialize the List<KeyValuePair<string, string>> Keys using the key names as element names, the standard solution is to implement IXmlSerializable on an appropriate type. It's a bit of a nuisance but not awful since your pair values are primitive types (strings) rather that complex types requiring nested serializations.
For instance, you could use the XmlKeyTextValueListWrapper from Serialize Dictionary member to XML elements and data:
public class XmlKeyTextValueListWrapper<TValue> : CollectionWrapper<KeyValuePair<string, TValue>>, IXmlSerializable
{
public XmlKeyTextValueListWrapper() : base(new List<KeyValuePair<string, TValue>>()) { } // For deserialization.
public XmlKeyTextValueListWrapper(ICollection<KeyValuePair<string, TValue>> baseCollection) : base(baseCollection) { }
public XmlKeyTextValueListWrapper(Func<ICollection<KeyValuePair<string, TValue>>> getCollection) : base(getCollection) {}
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.ReadXml(reader, this, converter);
}
public void WriteXml(XmlWriter writer)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.WriteXml(writer, this, converter);
}
#endregion
}
public static class XmlKeyValueListHelper
{
public static void WriteXml<T>(XmlWriter writer, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
}
public static void ReadXml<T>(XmlReader reader, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var key = XmlConvert.DecodeName(reader.Name);
string value;
if (reader.IsEmptyElement)
{
value = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
value = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string,T>(key, (T)typeConverter.ConvertFromInvariantString(value)));
}
// Move past the end of the list element
reader.ReadEndElement();
}
public static void CopyTo<TValue>(this XmlKeyTextValueListWrapper<TValue> collection, ICollection<KeyValuePair<string, TValue>> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (collection == null)
dictionary.Clear();
else
{
if (collection.IsWrapperFor(dictionary)) // For efficiency
return;
var pairs = collection.ToList();
dictionary.Clear();
foreach (var item in pairs)
dictionary.Add(item);
}
}
}
public class CollectionWrapper<T> : ICollection<T>
{
readonly Func<ICollection<T>> getCollection;
public CollectionWrapper(ICollection<T> baseCollection)
{
if (baseCollection == null)
throw new ArgumentNullException();
this.getCollection = () => baseCollection;
}
public CollectionWrapper(Func<ICollection<T>> getCollection)
{
if (getCollection == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
}
public bool IsWrapperFor(ICollection<T> other)
{
if (other == Collection)
return true;
var otherWrapper = other as CollectionWrapper<T>;
return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
}
ICollection<T> Collection { get { return getCollection(); } }
#region ICollection<T> Members
public void Add(T item)
{
Collection.Add(item);
}
public void Clear()
{
Collection.Clear();
}
public bool Contains(T item)
{
return Collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Collection.Count; }
}
public bool IsReadOnly
{
get { return Collection.IsReadOnly; }
}
public bool Remove(T item)
{
return Collection.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return Collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
Then use it like:
public class Error
{
[XmlElement("message")]
public string Message { get; set; }
List<KeyValuePair<string, string>> keys;
[XmlIgnore]
public List<KeyValuePair<string, string>> Keys
{
get
{
// Ensure keys is never null.
return (keys = keys ?? new List<KeyValuePair<string, string>>());
}
set
{
keys = value;
}
}
[XmlElement("keys")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XmlKeyTextValueListWrapper<string> XmlKeys
{
get
{
return new XmlKeyTextValueListWrapper<string>(() => this.Keys);
}
set
{
value.CopyTo(Keys);
}
}
}
Incidentally, the same solution will work with a public Dictionary<string, string> Keys property, just be sure that the dictionary is pre-allocated:
public class Error
{
[XmlElement("message")]
public string Message { get; set; }
Dictionary<string, string> keys;
[XmlIgnore]
public Dictionary<string, string> Keys
{
get
{
// Ensure keys is never null.
return (keys = keys ?? new Dictionary<string, string>());
}
set
{
keys = value;
}
}
[XmlElement("keys")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XmlKeyTextValueListWrapper<string> XmlKeys
{
get
{
return new XmlKeyTextValueListWrapper<string>(() => this.Keys);
}
set
{
value.CopyTo(Keys);
}
}
}
Ok, am I just not seeing how to do this or is there a fundamental problem with the approach?
Given the following desired public API:
[Fact]
public void GroupManagerCanAddNewGroupWithConnectionId()
{
var groupName = "ThisNewGroup";
var connectionId = "ThisNewConnection";
var sut = new GroupManager();
sut.Add(groupName, connectionId);
Assert.Contains(groupName, sut.Groups);
Assert.Contains(connectionId, sut.Groups[groupName]);
}
How do I achieve something like the following, which clearly is not legal as it does not compile:
private readonly ConcurrentDictionary<string, HashSet<string>> groups =
new ConcurrentDictionary<string, HashSet<string>>();
public IEnumerable<string> Groups { get { return this.groups.Keys; } }
public IEnumerable<string> Groups[string i] { get { return this.groups[i]; } }
public void Add(string groupName, string connectionSring)
{
groups.AddOrUpdate(
groupName,
new HashSet<string>(new string[1] { connectionSring }),
(conn, list) => { list.Add(conn); return list; });
}
Use an indexer in your class for accessing a dictionary by a key
public IEnumerable<string> this[string key]
{
get
{
return groups[key];
}
}
I knew it could be done. I added a wrapper class to manage access and then defined access by inheriting from IEnumerable and providing a property indexer. Then my group manager class became simple. Now my test passes.
public class GroupManager
{
private readonly GroupStore groups = new GroupStore();
public GroupStore Groups { get { return this.groups; } }
public void Add(string groupName, string connectionSring)
{
groups.Add(groupName, connectionSring);
}
}
public class GroupStore : IEnumerable<string>
{
private readonly ConcurrentDictionary<string, HashSet<string>> groupStore = new ConcurrentDictionary<string, HashSet<string>>();
public IEnumerable<string> this[string index] { get { return this.groupStore[index]; } }
public IEnumerator<string> GetEnumerator()
{
return groupStore.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
public void Add(string groupName, string connectionSring)
{
//groupStore.AddOrUpdate(...);
}
}
I define a class property algorithm as follows:
public InputParametersProperty InputParameters { get; set; }
public class InputParametersProperty
{
private Dictionary<string, object> inputParameters = new Dictionary<string, object>();
public object this[string name]
{
get { return inputParameters[name]; }
set
{
if (inputParameters == null)
inputParameters = new Dictionary<string, object>();
else
inputParameters.Add(name, value);
}
}
}
From another class I want to use the property of the form:
algorithm.InputParameters["populationSize"] = 100;
But I get the error: Object reference not set to an instance of an object
You're never instantiating the InputParameters property to anything. That's why you're gettin NullReferenceException.
Change:
public InputParametersProperty InputParameters { get; set; }
to:
private InputParametersProperty _inputParameters;
public InputParametersProperty InputParameters
{
get
{
return _inputparameters ?? (_inputparameters = new InputParametersProperty());
}
}
public class KeyValue : ProfileBase
{
public KeyValue()
{ }
public List<KeyValuePair<string, string>> pair
{
get { return (List<KeyValuePair<string,string>>)base["pair"]; }
set { base["pair"] = value; }
}
public string Name
{
get { return base["Name"] as string; }
set { base["Name"] = value; }
}
}
Whats the problem in this CustomProfile class.I am saving the list of key-value pairs but when i am accessing it show null in each pair, but it gives name correctly