POCO to Dictionary Conversion Techniques in C#

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 post we shall look at different approaches to convert a POCO to a dictionary in C#.

Disclaimer: The approaches discussed in this post works 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.

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.

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.

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.

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.

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>();
    }
}

On 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 post, 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 cost in speed.

  • 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.