Replacing MVC JavascriptSerializer with JSON.NET JsonSerializer

In ASP.NET MVC, the default JSON serializer is JavascriptSerializer. It is used when we return JsonResult by calling Controller#Json() or simply instantiate a JsonResult directly. It is also used during model binding by the JsonValueProviderFactory for deserializing request body to action parameter. Today I’m going to replace the JavascriptSerializer for serializing outgoing data.

There are a couple reasons why we want to replace the JavascriptSerializer. The most common one is the date format. It serializes DateTime into a proprietary format while browsers and other 3rd party serializers have adopted the ISO 8601 standard.

Another reason is this serializer cannot handle circular reference. Circular reference is quite common when using ORM such as Entity Framework and NHibernate. To handle that, one could disable proxy, disable lazy loading or apply ScriptIgnore attribute. To me, disabling proxy and lazy loading is probably not ideal as this is the main reason to use ORM in the first place. Applying ScriptIgnore is a sensible solution but you are out of luck if you’re using .NET 4.0 or below because of this issue.

So we need a better JSON serializer. The most popular one in .NET now is probably JSON.NET. It has better date serialization and circular reference handling. On top of that, it has a lot more features such as working with WCF DataContract and DataMember attributes and if you’re working with EF generated classes, MetadataType attribute. You can see how its features compare to the JavascriptSerializer on the JSON.NET home page. One more reason to use JSON.NET is its the default JSON serializer for ASP.NET Web API. This just may make your upgrade in future a little easier.

To allow existing code to keep working after replacing the JSON serializer, I would create two classes, BaseController and JsonNetResult. BaseController is an abstract class that sits between your controller and the MVC abstract Controller class. You may already have one since it’s likely you have some project-specific methods or properties common in all your controllers. If you don’t, then just create one and have the existing controllers extend from it.

Now we’ll override the Json method used by other Json overrides in the MVC Controller class to return JsonNetResult.

public abstract class BaseController : Controller
{
    protected override JsonResult Json(object data, string contentType,
        Encoding contentEncoding, JsonRequestBehavior behavior)
    {
        return new JsonNetResult
                   {
                       Data = data,
                       ContentType = contentType,
                       ContentEncoding = contentEncoding,
                       JsonRequestBehavior = behavior
                   };
    }
}

Here is the JsonNetResult class.

public class JsonNetResult : JsonResult
{
    public JsonNetResult()
    {
        Settings = new JsonSerializerSettings
                       {
                           ReferenceLoopHandling = ReferenceLoopHandling.Error
                       };
    }

    public JsonSerializerSettings Settings { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            throw new InvalidOperationException("JSON GET is not allowed");

        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;

        if (this.ContentEncoding != null)
            response.ContentEncoding = this.ContentEncoding;
        if (this.Data == null)
            return;

        var scriptSerializer = JsonSerializer.Create(this.Settings);

        using(var sw = new StringWriter())
        {
            scriptSerializer.Serialize(sw, this.Data);
            response.Write(sw.ToString());
        }
    }
}

JsonNetResult inherits JsonResult to satisfy the signature of the Controller#Json method. One drawback with this approach is both MaxJsonLength and RecursionLimit properties are not used in my implementation since there are no equivalent properties in JSON.NET. Also notice in the above implementation, I have ReferenceLoopHandling set to Error so that circular reference still results in error.

Now by having your controllers inheriting from BaseController, they will start using JSON.NET to do JSON serialization. Here are the two ways to make use of the new serializer in a controller.

// calling overridden Json() method
return Json(data, JsonRequestBehavior.AllowGet);

// instantiating JsonNetResult
var result = new JsonNetResult
                 {
                     Data = data,
                     JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                     Settings = { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }
                 };
return result;

Notice the second way include setting ReferenceLoopHandling to Ignore. With this, the serializer will skip any circular reference. This is one option to handle circular reference by simply ignoring it. This does not work exactly the same as the ScriptIgnore attribute so JSON.NET provides the equivalent JsonIgnore attribute.

If you want more flexibility, you can update MemberSerialization of JsonSerializerSettings to OptIn to serialize only marked members.

Have fun coding!!

2 thoughts on “Replacing MVC JavascriptSerializer with JSON.NET JsonSerializer

  1. Pingback: Using AutoMapper for More JSON Serialization Flexibility | Ricky Wan's Technical Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s