Handlebars.Net & JSON templates

Handlebars.Net & JSON templates

I ♥️ Handlebars! So I was very very very happy to see that Handlebars was ported to .NET! It is a mega flexible templating engine as it can easily be extended. I'm working on a project where I need to parse objects via JSON templates to JSON strings. This article will show how to instruct Handlebars to parse into JSON and add some nice error messages if your template fails.

Encoding JSON text

The trick is to add a special ITextEncoder to the IHandlebars instance. I've created a JsonTextEncoder that will do the proper escaping:

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

I've added some extra methods for convenience.

Example

You can use the static class to parse JSON templates and receive a JSON string back:

string source =
@"
{ 
  ""title"": ""{{title}}"",
  ""body"": ""{{body}}""
}";

var template = JsonHandlebarsDotNet.Compile(source);

var data = new
{
    title = "My new post",
    body = "This\nis\nmy \"first post\"!"
};

var result = template(data);

/* result is: 
{
  "title": "My new post",
  "body": "This\nis\nmy \"first post\"!"
}
*/

Improve JSON debugging

Debugging those JSON results is quite a challenge, as it is super easy to forget a comma. So we need something to help us detect validation errors quickly. Let's add a new method that also parses the result and wraps any exception:

public static string Parse(string template, object input)
{
    var t = Compile(template);
    var json = t(input);

    try
    {
        JsonConvert.DeserializeObject<dynamic>(json);
    }
    catch (Exception ex)
    {
        throw new InvalidJsonException(ex, json);
    }

    return json;
}

Let's implement the InvalidJsonException class that parsed the message of the exception that was thrown:

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

Now our errors contain a visual representation of where the error is, making debugging a lot easier. Here we have an example of a template containing the error. We've omitted a , between the .NET and C# tags.

string source =
@"
{ 
  ""title"": ""{{title}}"",
  ""author"": ""Kees C. Bakker"",
  ""authorUrl"": ""https://keestalkstech.com/about-me/"",
  ""created"": ""2202-09-17 11:52"",
  ""categories"": [ ""programming"" ],
  ""tags"": [ "".NET"" ""C#"", ""HBS"" ],
  ""body"": ""{{body}}""
}";

var data = new
{
    title = "My new post",
    body = "First!"
};

try
{
    var json = JsonHandlebarsDotNet.Parse(source, data);
    Console.WriteLine(json);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

When we execute the code, we'll get the following error message:

After parsing a value an unexpected character was encountered: ". Path 'tags[0]', line 8, position 21.

06 |  "created": "2202-09-17 11:52",
07 |  "categories": [ "programming" ],
08 |  "tags": [ ".NET" "C#", "HBS" ],
-----------------------^
09 |  "body": "First!"
10 | }

One might consider pre-parsing the template for errors, but that won't help you, because the template might have perfectly valid handlebar tags, which are invalid in JSON.

Inject a JSON Template Generator

While a static class will work for many use cases, it might be better to use dependency injection. Let's create a IJsonTemplateGenerator:

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

This object has everything to help us in parsing templates:

Now, let's implement it:

[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"]

Manifest streams: templates as embedded resources

I would like to ship my templates as an embedded resource with the DLL. So let's create some extension methods to help us to

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

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

We can now use these methods to read templates directly from our DLL:

var template = _generator.ParseWithManifestResourceToObject( 
    "BotZero.Handlers.Templates.Poker.json.hbs",
    vote);

Changelog

I've added the code to my .NET gallery, check: keestalkstech-code-gallery/06.handlebars.