Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which approach to templating in C# should I take?

What I have I have templates that are stored in a database, and JSON data that gets converted into a dictionary in C#.

Example: 

Template: "Hi {FirstName}"

Data: "{FirstName: 'Jack'}"

This works easily with one level of data by using a regular expression to pull out anything within {} in the template.

What I want I would like to be able to go deeper in the JSON than the first layer.

Example:

Template: "Hi {Name: {First}}"

Data: "{Name: {First: 'Jack', Last: 'Smith'}}"

What approach should I be taking? (and some guidance on where to start with your pick)

  1. A regular expression
  2. Not use JSON in the template (in favor of xslt or something similar)
  3. Something else

I'd also like to be able to loop through data in the template, but I have no idea at all where to start with that one!

Thanks heaps

like image 367
jamie-wilson Avatar asked Nov 06 '11 02:11

jamie-wilson


3 Answers

You are in luck! SmartFormat does exactly as you describe. It is a lightweight, open-source string formatting utility.

It supports named placeholders:

var template = " {Name:{Last}, {First}} ";

var data = new { Name = new { First="Dwight", Last="Schrute" } };

var result = Smart.Format(template, data);
// Outputs: " Schrute, Dwight " SURPRISE!

It also supports list formatting:

var template = " {People:{}|, |, and} ";

var data = new { People = new[]{ "Dwight", "Michael", "Jim", "Pam" } };

var result = Smart.Format(template, data);
// Outputs: " Dwight, Michael, Jim, and Pam "

You can check out the unit tests for Named Placeholders and List Formatter to see plenty more examples!

It even has several forms of error-handling (ignore errors, output errors, throw errors).

Note: the named placeholder feature uses reflection and/or dictionary lookups, so you can deserialize the JSON into C# objects or nested Dictionaries, and it will work great!

like image 195
Scott Rippey Avatar answered Oct 25 '22 00:10

Scott Rippey


Here is how I would do it:

Change your template to this format Hi {Name.First}

Now create a JavaScriptSerializer to convert JSON in Dictionary<string, object>

JavaScriptSerializer jss = new JavaScriptSerializer();
dynamic d = jss.Deserialize(data, typeof(object));

Now the variable d has the values of your JSON in a dictionary.

Having that you can run your template against a regex to replace {X.Y.Z.N} with the keys of the dictionary, recursively.

Full Example:

public void Test()
{
    // Your template is simpler
    string template = "Hi {Name.First}";

    // some JSON
    string data = @"{""Name"":{""First"":""Jack"",""Last"":""Smith""}}";

    JavaScriptSerializer jss = new JavaScriptSerializer();

    // now `d` contains all the values you need, in a dictionary
    dynamic d = jss.Deserialize(data, typeof(object));

    // running your template against a regex to
    // extract the tokens that need to be replaced
    var result = Regex.Replace(template, @"{?{([^}]+)}?}", (m) =>
        {
            // Skip escape values (ex: {{escaped value}} )
            if (m.Value.StartsWith("{{"))
                return m.Value;

            // split the token by `.` to run against the dictionary
            var pieces = m.Groups[1].Value.Split('.');
            dynamic value = d;

            // go after all the pieces, recursively going inside
            // ex: "Name.First"

            // Step 1 (value = value["Name"])
            //    value = new Dictionary<string, object>
            //    {
            //        { "First": "Jack" }, { "Last": "Smith" }
            //    };

            // Step 2 (value = value["First"])
            //    value = "Jack"

            foreach (var piece in pieces)
            {
                value = value[piece]; // go inside each time
            }

            return value;
        });
}

I didn't handle exceptions (e.g. the value couldn't be found), you can handle this case and return the matched value if it wasn't found. m.Value for the raw value or m.Groups[1].Value for the string between {}.

like image 24
BrunoLM Avatar answered Oct 24 '22 23:10

BrunoLM


Have you thought of using Javascript as your scripting language? I had great success with Jint, although the startup cost is high. Another option is Jurassic, which I haven't used myself.

If you happen to have a Web Application, using Razor maybe an idea, see here.

Using Regex or any sort of string parsing can certainly work for trivial things, but can get painful when you want logic or even just basic hierarchies. If you deserialize your JSON into nested Dictionaries, you can build a parser relatively easily:

// Untested and error prone, just to illustrate the concept
var parts = "parentObj.childObj.property".split('.');
Dictionary<object,object> current = yourDeserializedObject;
foreach(var key in parts.Take(parts.Length-1)){
    current = current[key];
}
var value = current[parts.Last()];

Just whatever you do, don't do XSLT. Really, if XSLT is the answer then the question must have been really desperate :)

like image 20
Michael Stum Avatar answered Oct 25 '22 00:10

Michael Stum