Object Text Tokens: Simple .Net Object Templating

At my day job, we are revamping our document generation processes. We need to generate complex business documents containing lots of text. One of the requirements was to allow a power user to add in tokens that get replaced with data. In C# we do this with string interpolation. Formatted strings are created using a special syntax {variable}:

string thing1 = "Thing 1";
string thing2 = "Thing 2";
var result = $"{thing1} and {thing2}";

In the above example the text inside the {} is replaced with a variable value, resulting in the text “Thing 1 and Thing 2”.

What we needed to do, was to allow similar functionality but at the level of an object. Each object property of a string data type could contain a templated string. The templated strings would then get filled with values from object properties.

Why? If you are still having a hard time imagining why you'd want/need to do this read the following.

StackOverflow: C# Reflection: replace all occurrence of property with value in text

Now, imagine you store your data in database and you serialize it to JSON. You hydrate a C# object from the JSON and have an object that looks like this.

public class Contract {
   public string Customer {get; set;}
   public DateTime PurchaseDate {get;set;}
   public string Product {get;set;}
   public string Quantity {get;set;}
   public string FinePrint {get;set;}
}

Your contract will always contain a section of fine print. The fine print needs the values from several fields in your object. This would be an easy thing to solve with string interpolation.

$“This contract is between {Customer} and business X. Your purchase of {Quantity} – {Product} shall be delivered 20 years from the data of purchase – sucker”

Let's expand on this hypothetical. Now imagine that your object looks like this.

public class Contract {
    public CustomerModel Customer {get; set;}
    public DateTime PurchaseDate {get;set;}
    public ProductModel Product {get;set;}
    public PriceModel Product {get;set;} 
    public QuantityModel Quantity {get;set;}
    public string FinePrint {get;set;}
    public List<string> Terms {get;set;}
}

Notice that we now have nested objects inside the Contract. Imagine that you don't control the fine print, the terms or anything else. You don't know which of these fields will need a value from any other. The users of your software want to control what data gets included in the fine print.

With ObjectTextTokens we can give the users control of text templating. All that's required is for them to know the object property structure and a simple syntax. For templating, we'll replace text been @ symbols. For object and property access we'll dot into things. @object.property@.

“This contract is between @customer.name@ and business X. Your purchase of @quantity.total@ – @product.name@ shall be delivered right away. The price at time of delivery will not exceed the agreed upon price of @price.total@”

Originally, I solved this issue with tiny little chunk of JavaScript on the client. Later I realized we needed values from several calculated fields server side. It's a pretty easy problem when you don't have to worry about types.

export function tokenator(object: Object) {
        return objectTokenatorIterator(object, object);
}

function objectTokenatorIterator(inputObj: Object, lookupObj: Object) {
        if (!inputObj) {        return;
    }
    Object.keys(inputObj).forEach((k, i) => {
        if (typeof inputObj[k] === "object") {
            return objectTokenatorIterator(inputObj[k], inputObj);
        } else {
            let fieldContents = inputObj[k] as string;
            let matchedTokens = fieldContents.toString().match(/(@\w*@|@\w*\.\w.*@)/g);
            if (matchedTokens && matchedTokens.length > 0) {
                matchedTokens.forEach(t => {
                    let fieldPath = t.replace("@", '').replace("@", "");
                    let content = getNestedObjProperty(fieldPath, lookupObj);
                    fieldContents = fieldContents.replace(t, content);
                });
                inputObj[k] = fieldContents;
            }
        }
    });
    return inputObj;
}

function getNestedObjProperty(path, obj) {
    return path.split('.').reduce(function (prev, curr) {
        return prev ? prev[curr] : null
    }, obj || self)
}

Moving this code over to the server side provided a bit more of a challenge. If this sounds like what you've been looking for, head on over to Github or download the NuGet package and check it out.