Today I was playing with Tabulator, a great open-source interactive table JavaScript library which I've already talked about a few times in the past (our Tabulazer Extension for Google Chrome is strongly based on it).
Such library requires some JavaScript in order to work, such as the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//define data array var tabledata = [ {id:1, name:"Oli Bob", progress:12, gender:"male", rating:1, col:"red", dob:"19/02/1984", car:1}, {id:2, name:"Mary May", progress:1, gender:"female", rating:2, col:"blue", dob:"14/05/1982", car:true}, {id:3, name:"Christine Lobowski", progress:42, gender:"female", rating:0, col:"green", dob:"22/05/1982", car:"true"}, {id:4, name:"Brendon Philips", progress:100, gender:"male", rating:1, col:"orange", dob:"01/08/1980"}, {id:5, name:"Margret Marmajuke", progress:16, gender:"female", rating:5, col:"yellow", dob:"31/01/1999"}, {id:6, name:"Frank Harbours", progress:38, gender:"male", rating:4, col:"red", dob:"12/05/1966", car:1}, ]; //initialize table var table = new Tabulator("#example-table", { data:tabledata, //assign data to table autoColumns:true, //create columns from data field names }); |
As you can see, when inizializing the Tabulator object we need to pass an array of columns (see the tabledata variable in the above example); such array is basically an array of JSON objects with some basic properties that helps the library to render the actual table, such as name, width, and so on.
The problem
In my ASP.NET Core projects I often end-up generating the Tabulator's JavaScript code dinamically from the back-end using the Json.NET library by Newtonsoft - which I actually like more than the built-in alternative offered by the framework (System.Text.Json) for a number of reasons that I've explained in this post. Such library works great, but in order to create valid JSON it automatically quotes both the object names and the values (if they are strings).
This basically means that such JSON object:
1 |
{ id:4, name:"Brendon Philips", formatter: "plaintext" } |
gets serialized in the following way:
1 |
{ "id":4, "name":"Brendon Philips", "formatter": "plaintext" } |
Such serialization standard is absolutely fine for Tabulator for almost any possible scenario... except when the value is a JavaScript function, variable or reference.
In other words, if we want to assign a JavaScript function to the formatter parameter in the following way...
1 2 3 4 5 6 7 |
var myFormatterFunc = function() { /* todo */ } // [...] { id:4, name:"Brendon Philips", formatter: myFormatterFunc } // [...] |
... we would end up with the following:
1 |
{ "id":4, "name":"Brendon Philips", "formatter": "myFormatterFunc" } |
which won't be executed correctly by the Tabulator script, thus ending up in a format error.
The solution
Luckily enough I've stumbled upon this StackOverflow's answer that helped me to develop the following class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Ryadel.Web { /// <summary> /// Converts string values without quotes, allowing to pass JS functions references (or even a whole JS function) as values in a JSON result. /// ref.: https://stackoverflow.com/a/15662075/1233379 /// /// BEFORE: { "name": "value" } /// AFTER: { "name": value } /// </summary> public class JavaScriptValueConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(string); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return reader.Value; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteRawValue((string)value); } } } |
As we can see, this is a custom JsonConverter (based upon the JsonConverter base class available from the Newtonsoft.Json namespace) that allows to output a string value without quotes.
Once implemented, such converter can be used to decorate the property of the ViewModel that hosts the JavaScript values, in the following way:
1 2 3 4 5 6 |
public class MyViewModel { [JsonProperty(PropertyName = "formatter")] [JsonConverter(typeof(JavaScriptValueConverter))] public string Formatter { get;set; } } |
When serialized, such class will produce the following output...
1 |
{ "formatter": myFormatterFunc } |
... which is precisely what we need.
Conclusion
That's it, at least for now: I hope that this simple yet effective workaround will help other developers to achieve what they want without losing too much time or having to re-engineer their whole process.