POCO to Dictionary Conversion Techniques in C#

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

Reflection

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

Newtonsoft.Json

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

System.Text.Json (C# 8.0+)

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

LINQ and Custom Logic

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

Using ExpandoObject

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.


Similar Articles