Converting Dictionary to Generic List<T> With Custom Attribute

Bora Kaşmer
Geek Culture
Published in
10 min readNov 27, 2022

If you have some string key and object values in the dictionary and want to convert to the “List of <T>” model and need some specific operations for some properties, this article is for you.

In this article, we will take two different approaches. One of them is Dictionary Extension Class with Mapped Config file and the other is Better Performance Of Dictionary Extension Class.

Model/SentMail.cs: This is our converted Dictionary List<T> model. We will convert Dictionary key-value data to “List<SenMail>”. “MailType” is int Enum field. We will write a custom “[JsonData]” attribute and we will sign the Recipient property with this attribute. We will talk about this later in the article.

Not: “Konu”, is especially defined in Turkish. It means subject. We will map it with the “subject” keyword, by using config later.

namespace BlogDictionaryToList.Model
{
public class SentMail
{
public string Email { get; set; }
public string MailId { get; set; }
public int MailType { get; set; }
public string FolderName { get; set; }
public string From { get; set; }
public string Konu { get; set; }
public string SenderName { get; set; }
public DateTime PostTime { get; set; }

[JsonData]
public string Recipient { get; set; }
}
}

CustomAttribute/JsonData.cs: This is our custom attribute. It is used for only flagging a property of a class. This property is a complex type of dictionary value. And we will convert it to the string when we see this flag.

namespace BlogDictionaryToList.CustomAttribute
{
[AttributeUsage(AttributeTargets.All)]
public class JsonData : Attribute
{
public JsonData()
{
}
}
}

StaticConverter/ToJson(): This extension is used for converting object to string.

 public static string ToJson(this object value)
{
return JsonConvert.SerializeObject(value);
}

Enums/MailType.cs: This is integer MailType property of SentMail.

namespace BlogDictionaryToList.Enums
{
public enum MailType
{
Inbox = 1,
JunkEmail = 2,
Drafts = 3,
SentItems = 4,
DeletedItems = 5,
Others = 6
}
}

Model/Email.cs: This is our complex type of “Recipient” keyword of dictionary value. We will convert this, List of Classes to a string for mapping the Recipient property of the “SentMail” class.

namespace BlogDictionaryToList.Model
{
public class Email
{
public string EmailContent { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public SenderType SenderType { get; set; }
}
}

Enum/SenderType.cs: This is the Email class’s property SenderType. It is an integer Enum.

namespace BlogDictionaryToList.Enums
{
public enum SenderType
{
To = 1,
Cc = 2,
Bcc = 3
}
}

Program.cs(1): Firstly, let’s fill the ten thousand “SentMail” model into the dictionary by key and value. “Recipients” is the list of “Email”. We will set it to the “Recipient” field in the dictionary. “uniqueKey” is the ignore key. We will not save it to the DB. We used it, to not allow the duplicate row. (”if (dictList.Any(dic => dic[“uniqueKey”].ToString() == uniqueKey))”)

using BlogDictionaryToList.Enums;
using BlogDictionaryToList.Model;

List<Dictionary<string, object>> dictList = new List<Dictionary<string, object>>();
List<Email> Recipients = new List<Email>();
Recipients.Add(new Email() { EmailContent = "Test Email", SenderType = SenderType.To });
Recipients.Add(new Email() { EmailContent = "Test Email 2", SenderType = SenderType.Cc });

for (var i = 0; i < 10000; i++)
{
Dictionary<string, object> dict = new Dictionary<string, object>();

var uniqueKey = $"bora@borakasmer.com{i}" + "|" + 2 + "|" + 653 + i;
dict.Add("uniqueKey", uniqueKey);
dict.Add("Email", $"bora.kasmer@keepnetlabs.com{i}");
dict.Add("MailId", 653 + i);
dict.Add("MailType", MailType.SentItems);
dict.Add("FolderName", $"Bora's Folder{i}");
dict.Add("From", $"Bora{i}");
dict.Add("Subject", $"Test Converter{i}");
dict.Add("SenderName", $"Bora{i}");
dict.Add("PostTime", DateTime.Now);
dict.Add("Recipient", Recipients);

//Check for eliminate the same row.
if (dictList.Any(dic => dic["uniqueKey"].ToString() == uniqueKey))
{
var dic = dictList.First(dic => dic["uniqueKey"].ToString() == uniqueKey);
dictList.Remove(dic);
}
dictList.Add(dict);
}
One item in the dictionary after filled by 10000 SentMail items.

DictionaryToList<T> Extension (Version 1): The first approach, writing a static extension class. Next, we will add new features to this class.

1-) We will start a timer, for calculating the total time. We will create a generic List<T>, which we will fill and return it. We will loop every dictionary in the dictionary list.

2-)We will create, a generic “T” class by using reflection. We will loop every item in the dictionary. “uniqueKey” is ignore key. We can use it only for custom operations. For example, it could be used for checking whether the same record is existing or not. There is no matching column on DB for the “uniqueKey”. Every key is the name of the class’s property. We will get “PropertyInfo” with a string key by using reflection. We will get the “Type” of property. And the check is it null or not.

3-) As we talked about at the beginning of this article[JsonData] Recipient property of the SentMail” is string type. But in the Hash, we set Recipient as a complex type so we have to convert this field to a JSON string. We will check the “JsonData” custom attribute. If any property has this attribute, we will convert it to a string with the custom “ToJson()” extension. Later we will improve the performance for this part. And finally, we will set this property with the hash value.

4-) Finally, we will add the SendMail model to the List, write the total passed time and return the list.

 public static class StaticConverter
{
public static List<T> DictionaryToList<T>(this IEnumerable<Dictionary<string, object>> dictionary,
Dictionary<string, string> ColumnMatchTable)
{
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
List<T> list = new List<T>();
foreach (Dictionary<string, object> dict in dictionary)
{
T model = Activator.CreateInstance<T>();

foreach (KeyValuePair<string, object> dic in dict)
{
//uniqueKey is Ignore Property
if (dic.Key != "uniqueKey") //Unique Dictionary Key
{
PropertyInfo propertyInfo = model.GetType().GetProperty(dic.Key);

Type t = propertyInfo.PropertyType;
t = Nullable.GetUnderlyingType(t) ?? t;
object safeValue;

if (Attribute.IsDefined(propertyInfo, typeof(JsonData)))
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : dic.Value.ToJson();
else
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : Convert.ChangeType(dic.Value, t);

propertyInfo.SetValue(model, safeValue, null);
}
}
list.Add(model);
}
Console.WriteLine("Total Time:" + watch.Elapsed);
return list;
}
}

Extensions/ToJson(): It is used for converting an object (class) to a string.

public static string ToJson(this object value)
{
return JsonConvert.SerializeObject(value);
}

“Positive attitude plus effort equals performance.”

— Tommy Tuberville

Mapped Different fields name between Dictionary and Class

We will match dictionary “Subject” key with “Konu” property of SentMail class.

Mapped Dictionary[“Subject”] with SentMail.Konu

StaticConverter/CheckedMapedKey(): We will map the dictionary string key value and model property name with this method. We will send dictionary config values and find the match of the string key parameter with this method.

       public static string CheckedMapedKey(string key, Dictionary<string, string> ColumnMatchTable)
{
if (ColumnMatchTable.Count > 0)
{
if (ColumnMatchTable.TryGetValue(key, out string value))
{
return value;
}
}
return key;
}

DictionaryToList<T> Extension (Version 2):

We will add the “Dictionary ColumnMatchTable” parameter to this extension. And mapped different string dictionary keys and class property names.

public static List<T> DictionaryToList<T>(this IEnumerable<Dictionary<string, object>> dictionary, Dictionary<string, string> ColumnMatchTable)
{
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
List<T> list = new List<T>();
foreach (Dictionary<string, object> dict in dictionary)
{
T model = Activator.CreateInstance<T>();

foreach (KeyValuePair<string, object> dic in dict)
{
//PropertyInfo propertyInfo = model.GetType().GetProperty(dic.Key);
PropertyInfo propertyInfo = model.GetType().GetProperty(CheckedMapedKey(dic.Key, ColumnMatchTable));

Type t = propertyInfo.PropertyType;
t = Nullable.GetUnderlyingType(t) ?? t;
object safeValue;

if (Attribute.IsDefined(propertyInfo, typeof(JsonData)))
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : dic.Value.ToJson();
else
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : Convert.ChangeType(dic.Value, t);

propertyInfo.SetValue(model, safeValue, null);
}
list.Add(model);
}
Console.WriteLine("Total Time:" + watch.Elapsed);
return list;
}

Example of Map Config Dictionary: We will match different column names with this method.

 var columnMatchTable = new Dictionary<string, string>() {
{"Subject", "Konu" }
};

“Details make perfection, and perfection is not a detail.”

Leonardo Da Vinci

Improve The Performance:

1-) For every item in the dictionary, we don’t have to check the attributes of every property. Only checking attributes for the first item, is enough. “Attribute.IsDefined()” reflection method consumes a lot of resources. That’s why we should stay as far away as possible. So we will add “CheckAttribute()” static Method. It is used for checking the attribute of properties. And if it finds an attribute, it will add it to “AttributeDictionaryList” with its property name and attribute name.

It is used for checking attribute of properties.

2-) List<T> is our return model and “AttributeDictionaryList” is a list, which is used for storing attributes with model properties. So when we convert the dictionary to a list for the first item, we will find the attribute of the properties and store them in the AttributeDictionaryList. For the next models, we will get their attributes, from this list. So we will improve the performance.

3-) We will change “Attribute.IsDefined()” with the custom “CheckAttribute()” method and after the first one, we can get attributes of property from the Dictionary.

This is the performance test between using Reflection(Attribute.IsDefined()) for all items and using only one item(CheckAttribute()).

Performance Test

“That’s been one of my mantras — focus and simplicity. Simple can be harder than complex…”

— Steve Jobs

DictionaryToList<T> Extension (FINAL Version):

        /// <summary>
/// Convert Dictionary To List<T>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dictionary"></param>
/// <param name="ColumnMatchTable"> var columnMatchTable = new Dictionary<string, string>() { {"Subject", "Konu" } }; </param>
/// <returns></returns>
public static List<T> DictionaryToList<T>(this IEnumerable<Dictionary<string, object>> dictionary,
Dictionary<string, string> ColumnMatchTable)
{
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
List<T> list = new List<T>();
Dictionary<string, bool> AttributeDictionaryList = new Dictionary<string, bool>(); //NEW
foreach (Dictionary<string, object> dict in dictionary)
{
T model = Activator.CreateInstance<T>();

string modelName = model.GetType().ToString();//NEW

foreach (KeyValuePair<string, object> dic in dict)
{
//uniqueKey is Ignore Property
if (dic.Key != "uniqueKey") //Unique Dictionary Key
{
//PropertyInfo propertyInfo = model.GetType().GetProperty(dic.Key);
PropertyInfo propertyInfo = model.GetType().GetProperty(CheckedMapedKey(dic.Key, ColumnMatchTable));

Type t = propertyInfo.PropertyType;
t = Nullable.GetUnderlyingType(t) ?? t;
object safeValue;

//Performance Improvement
//if (Attribute.IsDefined(propertyInfo, typeof(JsonData)))
if (CheckAttribute(modelName, propertyInfo, typeof(JsonData), AttributeDictionaryList))//NEW
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : dic.Value.ToJson();
else
safeValue = (dic.Value == null || dic.Value == System.DBNull.Value) ? null : Convert.ChangeType(dic.Value, t);

propertyInfo.SetValue(model, safeValue, null);
}
}
list.Add(model);
}
Console.WriteLine("Total Time:" + watch.Elapsed);
return list;
}

public static bool CheckAttribute(string modelName, PropertyInfo pi, Type type, Dictionary<string, bool> AttributeDictionaryList) //NEW
{
if (AttributeDictionaryList.ContainsKey(modelName + "." + pi.Name + "." + type.Name))
return AttributeDictionaryList[modelName + "." + pi.Name + "." + type.Name];
else
{
var result = Attribute.IsDefined(pi, type);
AttributeDictionaryList.Add(modelName + "." + pi.Name + "." + type.Name, result);
return result;
}
}

Program.cs(Final): UsingDictionaryToList()” extension.

1-) “dictList” is our test dictionary list. We will convert it to a list. List<Email> is our complex Recipient’s property test value.

2-) We will add 10000 rows to the dictionary lists. “uniqueKey” is our ignored key. It is a unique string name for every dictionary. There is no mapped property on DB for this key. We will use it for keeping singularity in the dictionary. If we find any record with the same key, we will remove and update it with the latest dictionary.

3-) “columnMatchTable” is our matching config list, like AutoMapper. We will run our DictionaryToList() extension 5 times for 5 * 10000 = 50000 records. And monitoring the result.

using BlogDictionaryToList;
using BlogDictionaryToList.Enums;
using BlogDictionaryToList.Model;

List<Dictionary<string, object>> dictList = new List<Dictionary<string, object>>();
List<Email> Recipients = new List<Email>();
Recipients.Add(new Email() { EmailContent = "Test Email", SenderType = SenderType.To });
Recipients.Add(new Email() { EmailContent = "Test Email 2", SenderType = SenderType.Cc });

for (var i = 0; i < 10000; i++)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
var uniqueKey = $"bora@borakasmer.com{i}" + "|" + 2 + "|" + 653 + i;
dict.Add("uniqueKey", uniqueKey);
dict.Add("Email", $"bora.kasmer@keepnetlabs.com{i}");
dict.Add("MailId", 653 + i);
dict.Add("MailType", MailType.SentItems);
dict.Add("FolderName", $"Bora's Folder{i}");
dict.Add("From", $"Bora{i}");
dict.Add("Subject", $"Test Converter{i}");
dict.Add("SenderName", $"Bora{i}");
dict.Add("PostTime", DateTime.Now);
dict.Add("Recipient", Recipients);
if (dictList.Any(dic => dic["uniqueKey"].ToString() == uniqueKey))
{
var dic = dictList.First(dic => dic["uniqueKey"].ToString() == uniqueKey);
dictList.Remove(dic);
}
dictList.Add(dict);
}

var columnMatchTable = new Dictionary<string, string>() {
{"Subject", "Konu" }
};
for (var s = 0; s < 5; s++)
{
var list = dictList.DictionaryToList<SentMail>(columnMatchTable);
}

“Nature is pleased with simplicity.”

— Isaac Newton.

Test Dictionary to List Performance

Conclusion:

In this article, we tried to convert a dictionary to a list without matching the columns one by one. While doing this we used generic and reflection class. I know reflection is a performance killer but if you have 100 column tables and readability is so important for you, you can use this extension without any hesitate. I tried to boost codes with storing attributes in a scoped dictionary. But if it is not enough for you, you can keep all “class.table.column.attributes” in a global static dictionary list on startup. And maybe you can overload “dictList.DictionaryToList(ColumnMatchTable, bool hasAttribute=true)”. If your class’s properties have no attributes, you can set the “hasAttribute” field to false and not make any check for “ToJson()” or any attribute operations.

Goodbye

See you until the next article.

“If you have read so far, first of all, thank you for your patience and support. I welcome all of you to my blog for more!”

Github: https://github.com/borakasmer/DictionaryToList

--

--

Bora Kaşmer
Geek Culture

I have been coding since 1993. I am computer and civil engineer. Microsoft MVP. Software Architect(Cyber Security). https://www.linkedin.com/in/borakasmer/