Making JSON data strong typed with TypeScript and CodeSmith

The situation

Imagine a scenario where you have a JSON service that returns data and you'd like to have it strongly typed on javascript powered client side.

Let's say there is an ASP.NET MVC application that goes by the name MvcApplication33 (yes, the 33rd in a row of test applications) . There are two models in there in two files under Models folder:

namespace MvcApplication33.Models
{
    public class Customer
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public int Age { get; set; }
        public Order[] Orders { get; set; }
    }
}

namespace MvcApplication33.Models
{
    public class Order
    {
        public double Amount { get; set; }
        public string Category { get; set; }
        public bool IsActive { get; set; }
    }
}

There is also a ApiController derived CustomersController like:

namespace MvcApplication33.Controllers
{
    public class CustomersController: ApiController
    {
        public IEnumerable<Customer> GetCustomers()
        {
            return new Customer[]{ new Customer
            {
                Name = "Tubo",
                Age = 22,
                Orders = new[]{ new Order { Amount = 54, IsActive = true, Category = "waste"} }
            }};
        }
    }
}

It simply returns an Customer array consisting of a single customer with a single order. The javascript, well jQuery, code that gets this data would look like:

$(function () {
        $.getJSON("/api/customers", null, function (d) {
            var jsonCustomer = d;
        });
    });

The problem

While the code above works there is a drawback (mostly for people coming from strong typed languages): on the client side there is no metadata information at design time whatsoever. One is left with dynamic constructs. TypeScript addresses this with interfaces. One could write matching TypeScript interfaces for C# types like:

interface ICustomer {
    Name: string;
    Surname: string;
    Age: number;
    Orders: IOrder[];
}
interface IOrder {
    Amount: number;
    Category: string;
    IsActive: bool;
}

and then rewrite retrieval like

$(function () {
    $.getJSON("/api/customers", null,
        d =>
        {
            var customer = <ICustomer[]>d;
        });
});

This way customer becomes an instance of a type that implements ICustomer which means properties are now strong typed and intellisense works. Of course this is only TypeScript design time sugar which doesn't reflect in generated javascript code but that's enough to prevent a zillion of typing and other "easy to catch at design time" errors.

There is one problem though. Typing interfaces is boring, error prone and there are synchronization issues between manually typed ones and their C# originals.

The solution

There is already all metadata information for our interfaces in C# code. Hence I created a CodeSmith template that automatically creates TypeScript interfaces based on their C# originals by parsing C# code. When C# code changes the template can be rećrun to recreate interfaces. Almost one click error-less operation.

Here is how it works: the template parses all C# files in a given folder and subfolders and generates matching TypeScript interfaces with corresponding namesapaces (using TypeScript modules). The template output for the given problem would look like:

// Autogenerated using CodeSmith and JsonGenerator
// © Righthand, 2013
module MvcApplication33.Models {
export interface ICustomer {
    Name: string;
    Surname: string;
    Age: number;
    Orders: IOrder[];
}
export interface IOrder {
    Amount: number;
    Category: string;
    IsActive: bool;
}

    export module Subform {
        export interface ISubclass {
            Tubo: bool;
        }

    }
}

Just for testing nested interfaces there is also interface ISubclass from original file located in Models subfolder named (Subform).

 

The TypeScript file that uses the mentioned autogenerated interfaces should reference autogenerated interfaces file using <reference> directive. Here is a sample TypeScript test:

/// <reference path="typings/jquery/jquery.d.ts" />
/// <reference path="../CodeSmith/JsonInterfaces.ts" />
module KnockoutTest {
    $(function () {
        $.getJSON("/api/customers", null,
            d =>
            {
                var customers = <MvcApplication33.Models.ICustomer[]>d;
                alert(customer[0].Name);
            });
    });
}

How to

The template comes in two parts. An actual CodeSmith template (JsonInterfaces.cst) and a .net assembly (KnockoutGenerator.Extractor.Parser.dll - name should give you a hint where all this is going in a next blog post) which is used to extract metadata from C# sources. The assembly code could be a part of CodeSmith template but it is easier to develop (read: debug) code within Visual Studio. The assembly in turn uses NRefactory (free C# parser and much more) which (v4.x) is a part of CodeSmith, so no additional files required.

Perhaps the easiest way to use the template is to include these two files in a project and use CodeSmith from within Visual Studio. The template requires a single property: FolderWithModels which is rather self explanatory. Excerpts from attached sample project:

The CodeSmith project content located in Project1.csp:

<?xml version="1.0" encoding="utf-8"?>
<codeSmith xmlns="http://www.codesmithtools.com/schema/csp.xsd">
  <propertySets>
    <propertySet name="JsonInterfaces" output="JsonInterfaces.ts" template="JsonInterfaces.cst">
      <property name="FolderWithModels">..\Models</property>
    </propertySet>
  </propertySets>
</codeSmith>

The project structure. I tend to put CodeSmith related stuff in CodeSmith folder. Feel free to arrange it otherwise.

folder structure

Right click on Project1.csp and Generate Outputs should (re)generate JsonInterfaces.ts. Open the output file and if Web Essentials and TypeScript are installed it should (re)generate final JavaScript file.

The project itself won't show any meaningful output (for now) but you can use JSON output in a strongly typed way.

Have fun and read next post which will talk about further enhancements for knockoutjs.

You can download my sample project (without NuGet packages, just sources) which includes everything you need (subfolder CodeSmith).

MvcApplication33.zip (566.78 kb)

NOTE: The CodeSmith template could be rewritten to T-4 (using the same support assembly) however, one should make sure that NRefactory assemblies are there otherwise parser won't work.