DateTime Object Management

DateTime object handling was one of challenges during JsAction developmet.
In this section i will describe what's the problem and how i decided to handle it.
Any comment is always appreciated.

Since JSON standard does not provide a way to handle Date JsObject, ASPNET built in javascript will not generate a Date object for a DateTime CRL type.

This is a problem, since i would expect to write this:
[JsAction]
public JsonResult GetDate()
{
    return Json(new{d = DateTime.Now});
}

And use it like a Date object:
JsActions.GetDate().done(function(data){alert(data.d.getYear());});

But it is not possible.
You can find a lot about this argument (a good point of start is http://www.google.com and simply write JSON DateTime serialization

So let's analyze MVC3 behavior with DateTime object.
At first keep this in mind:
  • MVC3 model binder CAN bind a Js Date() object to a DateTime CRL type, and this is good.
  • A MVC3 Controller returning a DateTime object will be serialized as a string with Date.ToString() value. And this is good.

And let's talk about a JSON object with a DateTime object inside.

The AJAX JSON serializer in ASP.NET encodes a DateTime instance as a JSON string. During its pre-release cycles, ASP.NET AJAX used the format "@ticks@", where ticks represents the number of milliseconds since January 1, 1970 in Universal Coordinated Time (UTC). A date and time in UTC like November 29, 1989, 4:55:30 AM would be written out as "@62831853071@." Although simple and straightforward, this format cannot differentiate between a serialized date and time value and a string that looks like a serialized date but is not meant to be deserialized as one. Consequently, the ASP.NET AJAX team made a change for the final release to address this problem by adopting the "\/Date(ticks)\/" format.

The new format relies on a small trick to reduce the chance for misinterpretation. In JSON, a forward-slash (/) character in a string can be escaped with a backslash (\) even though it is not strictly required. Taking advantage of this, the ASP.NET AJAX team modified JavaScriptSerializer to write a DateTime instance as the string "\/Date(ticks)\/" instead. The escaping of the two forward-slashes is superficial, but significant to JavaScriptSerializer. By JSON rules,
\/Date(ticks)\/
is technically equivalent to
/Date(ticks)/
but the JavaScriptSerializer will deserialize the former as a DateTime and the latter as a String. The chances for ambiguity are therefore considerably less when compared to the simpler "@ticks@" format from the pre-releases.

A common resolution to this problem is to output the value as an integer (containing ticks) and then use it as parameter for Date object costructor.
You can make this in server or client side.

Solution 1: use JSON.Net

Json.NET does't need any presentation: http://json.codeplex.com/
It has got a custom serializer and will handle DateTime objects too.
Why just do not use it?
Yes i know that we should always avoid to reinvent the wheel, but i would like to keep JsAction with no dependency, if really necessary. And was an occasion to learn something new.

Solution 2: Work on the server side

The standard asp net json serializer has some extension points, but noone convinced me (since also the best one required a minimal client side code.)
So i preferred to move all entirely on client side.
  • 2.1: Create a new Controller class and override Json function. This is the worse one: a small library like JsAction should not be so "invasive". And what if you have another function with it's custom base class controller?
  • 2.2: Create a JsonValueProvider for DateTime object and customize serialization
And then? Trasform it in what?

I think default serialization is quite fine. We have to work on client side.

Solution 3: Work on the client side

JsAction actually work with a client side solution. In few words, it "hooks" your data before they are returned by your function and costructs a Date object from standard serialized string.
jQuery.ajax(opts).then(trd);
Where trd is this function:
function trd(dt)
{
   for (var pr in dt)
   {
      if (typeof (dt[pr]) == 'object') trd(dt[pr]);
      if (typeof (dt[pr]) == 'string' && dt[pr].indexOf('/Date') == 0)
      {
         var m = dt[pr].replace(/\/Date\((-?\d+)\)\//, '$1');
         dt[pr] = new Date(parseInt(m));
      }
   }
}
The code is quite simple: if member in the current object is another object, make a recursive call.
If the current member is a string and contains a '/Date' ref, replace current member with a new Date object.
This is a good solution, and works well also for subobjects, but...wait!
jQuery.ajax(opts).then(trd);
This code gives a dependency with jQuery version: Deferred interface are infact aviable only if jQuery version > 1.5, and i do not like that.
So i had to "revert back" to success option in jQuery option object to make it compatible if version < 1.5

Last edited Feb 21, 2012 at 5:10 PM by XVincentX, version 5

Comments

No comments yet.