Recently, in one of my projects, I had to extract properties and values of a C# object to send as key-value pairs to a third-party REST API. Instead of manually extracting each property, I wanted a way to convert a Plain Old CLR Object (POCO) into a Dictionary<string, object>. After doing some research, I discovered multiple approaches to achieve this in C#. In this article, we shall look at different approaches to converting a POCO to a dictionary in C#.
Disclaimer. The approaches discussed in this post work well for simple objects with primitive data types (e.g., strings, integers). If you have complex objects, such as nested classes or collections, additional handling will be required.
Method 1. Using Reflection
Reflection is a powerful feature in C# that allows you to inspect and interact with object types and members at runtime. You can use it to iterate over the properties of a POCO object and create a dictionary from them.
using System;
using System.Collections.Generic;
using System.Reflection;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
}
public static class ObjectExtensions
{
public static Dictionary<string, object> ToDictionary<T>(this T obj)
{
var dictionary = new Dictionary<string, object>();
foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
dictionary[property.Name] = property.GetValue(obj, null);
}
return dictionary;
}
}
class Program
{
static void Main()
{
var person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
Dictionary<string, object> dictionary = person.ToDictionary();
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}
Explanation
- Reflection: The code uses the
GetProperties
method to obtain all public properties of the object. BindingFlags.Public
and BindingFlags.Instance
are used to specify that only instance properties should be included.
- Property Values: The
GetValue
method retrieves the value of each property for the given object.
Output
Method 2. Using Newtonsoft.Json
Newtonsoft.Json is a popular JSON library for .NET, which can be used to serialize and deserialize JSON. It can also be leveraged to convert a POCO object into a dictionary.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
}
class Program
{
static void Main()
{
var person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
var json = JsonConvert.SerializeObject(person);
var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}
Explanation
- Serialization: The
SerializeObject
method converts the POCO object into a JSON string.
- Deserialization: The
DeserializeObject
method then converts the JSON string into a dictionary.
Output
Method 3. Using System.Text.Json (C# 8.0+)
For .NET Core and .NET 5/6/7 projects, you can use System.Text.Json
a lightweight JSON library included in the .NET framework.
using System;
using System.Collections.Generic;
using System.Text.Json;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
}
class Program
{
static void Main()
{
var person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
var json = JsonSerializer.Serialize(person);
var dictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}
Explanation
- System.Text.Json: The
JsonSerializer
class provides methods to serialize and deserialize objects, similar to Newtonsoft.Json but built into the .NET framework.
- Serialization and Deserialization: The steps are the same as using Newtonsoft.Json but with the built-in
JsonSerializer
.
Output
Method 4. Using LINQ and Custom Logic
If you prefer a more custom approach, you can map properties manually using LINQ.
using System;
using System.Collections.Generic;
using System.Linq;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
}
public static class ObjectExtensions
{
public static Dictionary<string, object> ToDictionary<T>(this T obj)
{
return typeof(T)
.GetProperties()
.ToDictionary(prop => prop.Name, prop => prop.GetValue(obj, null));
}
}
class Program
{
static void Main()
{
var person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
Dictionary<string, object> dictionary = person.ToDictionary();
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}
Explanation
- LINQ: The
ToDictionary
method from LINQ is used to create a dictionary by iterating over each property of the object and capturing the name and value.
Output
Method 5. Using ExpandoObject
For more dynamic scenarios where you might need to add or remove properties, using ExpandoObject
could be a viable solution.
using System;
using System.Collections.Generic;
using System.Dynamic;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Occupation { get; set; }
}
class Program
{
static void Main()
{
var person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
var dictionary = ToExpandoDictionary(person);
foreach (var kvp in dictionary)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
public static IDictionary<string, object> ToExpandoDictionary<T>(T obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var property in typeof(T).GetProperties())
{
expando[property.Name] = property.GetValue(obj, null);
}
return expando;
}
}
Explanation
- ExpandoObject: This approach allows you to treat the dictionary as a dynamic object, giving more flexibility in terms of runtime modifications.
Output
Benchmarking Different Approaches
Let’s compare the performance of these methods using Benchmark.NET. We’ll use a simple POCO and run benchmarks to determine the efficiency of each approach.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Newtonsoft.Json;
using System.Dynamic;
using System.Reflection;
public static class PocoToDictionaryExtensions
{
public static Dictionary<string, object> ToDictionaryUsingReflection<T>(this T obj)
{
var dictionary = new Dictionary<string, object>();
foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
dictionary[property.Name] = property.GetValue(obj, null);
}
return dictionary;
}
public static Dictionary<string, object> ToDictionaryUsingNewtonSoftJson<T>(this T obj)
{
var json = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
}
public static Dictionary<string, object> ToDictionaryUsingSystemTextJson<T>(this T obj)
{
var json = System.Text.Json.JsonSerializer.Serialize(obj);
return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(json);
}
public static Dictionary<string, object> ToDictionaryUsingLinq<T>(this T obj)
{
return typeof(T)
.GetProperties()
.ToDictionary(prop => prop.Name, prop => prop.GetValue(obj, null));
}
public static IDictionary<string, object> ToDictionaryUsingExpando<T>(this T obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var property in typeof(T).GetProperties())
{
expando[property.Name] = property.GetValue(obj, null);
}
return expando;
}
}
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public string Occupation { get; set; }
}
public class PocoToDictionaryBenchmark
{
private Person _person;
public PocoToDictionaryBenchmark()
{
_person = new Person
{
Name = "John",
Age = 30,
Occupation = "Software Developer"
};
}
[Benchmark]
public Dictionary<string, object> UsingReflection()
{
return _person.ToDictionaryUsingReflection();
}
[Benchmark]
public Dictionary<string, object> UsingNewtonsoftJson()
{
return _person.ToDictionaryUsingNewtonSoftJson();
}
[Benchmark]
public Dictionary<string, object> UsingSystemTextJson()
{
return _person.ToDictionaryUsingSystemTextJson();
}
[Benchmark]
public Dictionary<string, object> UsingLinq()
{
return _person.ToDictionaryUsingLinq();
}
[Benchmark]
public IDictionary<string, object> UsingExpandoObject()
{
return _person.ToDictionaryUsingExpando();
}
}
internal class Program
{
private static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<PocoToDictionaryBenchmark>();
}
}
While running these benchmarks on my machine, I got the following result.
Method |
Mean |
Error |
StdDev |
Median |
UsingReflection |
418.4 ns |
8.16 ns |
8.01 ns |
416.5 ns |
UsingNewtonsoftJson |
3,459.0 ns |
119.22 ns |
351.53 ns |
3,518.5 ns |
UsingSystemTextJson |
2,911.6 ns |
123.34 ns |
363.68 ns |
3,037.3 ns |
UsingLinq |
447.6 ns |
28.22 ns |
83.21 ns |
476.5 ns |
UsingExpandoObject |
807.4 ns |
30.94 ns |
91.22 ns |
832.1 ns |
Summary
In this article, we explored five different approaches to converting a POCO to a dictionary and benchmarked them using Benchmark.NET. Here’s a quick performance comparison based on the result:
- Fastest methods: Reflection and LINQ performed the best, with execution times of 418.4 ns and 447.6 ns, respectively.
- Moderate performance: The ExpandoObject method took 807.4 ns, offering more flexibility but at a moderate speed cost.
- Slowest methods: Newtonsoft.Json (3,459.0 ns) and System.Text.Json (2,911.6 ns) were significantly slower. I was not expecting this, but I think this is due to the overhead of JSON serialization.