Handlebars.Net: Fun with [Flags]

Handlebars.Net: Fun with [Flags]

When working on a project, the need arose for better handling of enums decorated with a [Flags] attribute. In a previous article we explored how to use Handlebars.NET to generate JSON strings. In this article we'll build further on that JSON generator to add support for enums. We will also move away from the static JsonHandlebarsDotNet to an injectable version.

Through this article I'll be working with the following enum:

[Flags]
enum MenuItems
{
    None = 0,
    Pizza = 1,
    Fries = 2,
    Pancakes = 4,
    Meatballs = 8,
    Pasta = 16,
    StuffWithP = Pizza | Pancakes | Pasta,
    All = Pizza | Fries | Pancakes | Meatballs | Pasta | StuffWithP
};

You can see that it has all the nice features a flagged enum comes with: multiple combinations and a None value. It is going to be fun; fun with flags!

I'm not the only one that enjoys flags. Source: Big Bang Theory, Warner Bros. Television

Deconstructing a [Flags] enum

When we want to deconstruct our enum to the unique flag values, we see some special use cases:

The following method will deconstruct a flagged enum to a string array of unique values:

public static string[] DeconstructFlags(Enum items)
{
    if (items.GetType().GetCustomAttribute<FlagsAttribute>() == null)
    {
        throw new ArgumentException("Enum has no [Flags] attribute.", nameof(items));
    }

    // no value, no list
    var itemsValue = (int)(object)items;
    if (itemsValue == 0) return Array.Empty<string>();

    var result = new List<string>();

    foreach (var item in Enum.GetValues(items.GetType()))
    {
        if(item == null) continue;

        var value = (int)item;

        // skip combined flags
        if (!BitOperations.IsPow2(value))
        {
            continue;
        }

        if (items.HasFlag((Enum)item))
        {
            result.Add(item.ToString() ?? "");
        }
    }

    return result.ToArray();

}

This will return the following arrays:

void Echo(MenuItems items)
{
    Console.WriteLine($"{items}: {String.Join(" + ", DeconstructFlags(items))}");
}

Echo(MenuItems.None);
// outputs: None: None

Echo(MenuItems.Pasta);
// outputs: Pasta: Pasta

Echo(MenuItems.Fries | MenuItems.Pizza);
// outputs: Pizza, Fries: Pizza + Fries

Echo(MenuItems.StuffWithP);
// outputs: StuffWithP: Pizza + Pancakes + Pasta

Note how it is skipping the StuffWithP value and deconstructs properly.

Make Handlebars.NET respect the [Flags]

Now, we would love to iterate over our flagged enums using an {{#each }} statement. By default this is not possible and Handlebars.NET will just return the integer value of your enum. We'll make a FlaggedEnumObjectDescriptorProvider that fixes this:

var order = new
{
    items = MenuItems.Pizza | MenuItems.Pancakes
};

string source = @"
{ 
    ""order"": [
        {{#each items}}""{{this}}""{{#unless @last}},
        {{/unless}}{{/each}}
    ],
    ""orders"": ""{{items}}""
}";

var hbs = new JsonTemplateGenerator().Handlebars;
hbs.Configuration.ObjectDescriptorProviders.Add(new FlaggedEnumObjectDescriptorProvider());

var template = hbs.Compile(source);
var json = template(order);
Console.WriteLine(json);

This will output:

{
    "order": [
        "Pizza",
        "Pancakes"
    ],
    "orders": "Pizza, Pancakes"
}

The trick is to register a FlaggedEnumObjectDescriptorProvider that will handle the flagged enums.

Implementing an IObjectDescriptorProvider

I did some research on how to implement an IObjectDescriptorProvider and took the Handlebars.Net.Extension.Json array iterator as inspiration. The class checks if the type is an enum with the [Flags] attribute and returns an iterator that uses the DeconstructFlags method we've seen before.

[include-github-code src="https://github.com/KeesCBakker/keestalkstech-code-gallery/blob/main/06.handlebars/Ktt.JsonHandlebars/FlaggedEnumObjectDescriptorProvider.cs" wide=true usings="false" namespace="false" buster=1]

Notice how @first and @last are also implemented by this class.

Improve the JsonTemplateGenerator

We can now add the FlaggedEnumObjectDescriptorProvider to the ObjectDescriptorProviders of our JsonTemplateGenerator constructor:

[include-github-code src="https://github.com/KeesCBakker/keestalkstech-code-gallery/blob/main/06.handlebars/Ktt.JsonHandlebars/JsonTemplateGenerator.cs" wide=true usings="false" namespace="false" buster=1]

One might also consider adding the helpers to it: HandlebarsHelpers.Register(Handlebars);

Final thoughts

I just love Handlebars and the fact that the template engine is so versatile. It is super easy to handle enum flags.

Further reading

When writing this article, I stumbled upon some resources that might interest you:

Changelog