A Flat Dictionary Serializer in C#

Recently I have been working with rather large YAML config files and I was wondering if there was an easy way to diff them. Gron is a pretty cool command line tool which claims to make JSON greppable by transforming it into discrete assignments. It turns out that this format is also a great input for diff tools if you sort the assignments. I stumbled over this blog post in which the author presents a similar transformation for C# objects. After some minor tweaks I came up with my own implementation based on the first dynamic solution of said post:

public static class FlatDictionarySerializer
{
    public static Dictionary<string, string?> Serialize(
        object? obj,
        string name = "")
    {
        var dictionary = new Dictionary<string, string?>();

        Flatten(dictionary, obj, name);

        return dictionary;
    }

    private static void Flatten(
        IDictionary<string, string?> dictionary,
        object? obj,
        string prefix)
    {
        if (obj == null)
        {
            dictionary.Add(prefix, null);

            return;
        }

        var objType = obj.GetType();

        if (objType.IsValueType || objType == typeof(string))
        {
            dictionary.Add(prefix, obj.ToString());
        }
        else if (obj is IEnumerable subObjects)
        {
            var counter = 0;

            foreach (var subObj in subObjects)
            {
                Flatten(dictionary, subObj, $"{prefix}[{counter++}]");
            }
        }
        else
        {
            var properties = objType.GetProperties().Where(x => x.CanRead);

            foreach (var property in properties)
            {
                Flatten(
                    dictionary,
                    property.GetValue(obj),
                    string.IsNullOrEmpty(prefix)
                        ? property.Name
                        : $"{prefix}.{property.Name}");
            }
        }
    }
}

Note: Adding support for dictionaries only makes sense for a type signature of IDictionary<string, object> since other key types such as random objects might be hard to "stringify".

Update 2022-09-11: Another alternative is to use difftastic, which also supports JSON and YAML files.

Published: 2022-09-08