Tuesday, March 30, 2010

json2 date and function replacers

First off, you should probably visit Rick Strah's post here https://west-wind.com/Weblog/posts/729630.aspx#262650 first and then http://skysanders.net/subtext/archive/2010/02/18/wcf-to-json-dates-and-back-again.aspx because that is where I got this code, and idea from.

Like they state in their post, date serialization to and from from asmx or wcf services with json does not work right out of the box. Often times most developers will argue to just turn it into a string. Like so '20010/03/25' which works great if you are controlling both the client and the server code. Or if you are stubborn like me, and just want to pass native dates around without having to worry about converting them at the service level when you send data to the client or receive data.

Lastly I am using the json2 library from here http://www.json.org/ so I can get consistent results, regardless of browser. Using this code, you can easily pass native dates in and out of web services, and handle them as native js dates on the client.

I needed the function replacer to use with the jquery validate library http://bassistance.de/jquery-plugins/jquery-plugin-validation/ so that I could pass json objects to a web service using the validate remote rule. I will provide an example using this in a later post.

Example Usage:

var sd = new Date(2000, 1, 1);
var thing = {
plugin: 'json2',
version: 'tims hacked version',
customdata: function(bar) { return "foo"; },
startdate: sd
};
var encoded = JSON.stringifyAll(thing);
document.writeln(encoded);


This is what you should see in the browser: {"plugin":"json2","version":"tims hacked version","customdata":"foo","startdate":"/Date(949384800000-0000)/"}


(function json2Addons() {
// from https://west-wind.com/Weblog/posts/729630.aspx#262650
// with some fairly significant fixes by sky sanders
// http://skysanders.net/subtext/archive/2010/02/18/wcf-to-json-dates-and-back-again.aspx
//More code changes by Tim Cartwright - 3/25/2000, worked on the asmx date processing, also renamed the functions
//Tim Cartwright added new function replacer with new stringifyWithFuncs, and added stringifyAll
if (this.JSON && !this.JSON.parseWithDate) {

var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;
//var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
var reMsAjax = /Date\(([-+]?\d+[-+]?\d+)\)/;


JSON.parseWithDate = function(json) {
///
/// parses a JSON string and turns ISO or MSAJAX date strings
/// into native JS date objects
///

/// json with dates to parse
///
///

try {
var res = JSON.parse(json,
function(key, value) {
if (typeof value === 'string') {
var a = reISO.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
}
a = reMsAjax.exec(value);
if (a) {
//var b = a[1].split(/[-+,.]/);
//return new Date(b[0] ? +b[0] : 0 - +b[1]);
return new Date(eval(a[1]));
}
}
return value;
});
return res;
} catch (e) {
// orignal error thrown has no error message so rethrow with message
throw new Error("JSON content could not be parsed: " + e);
return null;
}
};
JSON.stringifyWithDate = function(json) {
///
/// Wcf specific stringify that encodes dates in the
/// a WCF compatible format ("/Date(9991231231)/")
/// Note: this format works ONLY with WCF.
/// ASMX can use ISO dates as of .NET 3.5 SP1
///

/// property name
/// value of the property

// choking on FF 3.6 date Wed Feb 24 2010 14:02:17 GMT-0700 (US Mountain Standard Time)
return JSON.stringify(json, DateReplacer)
};
JSON.stringifyWithFuncs = function(json) {
///
/// parses a JSON string and evaluates functions without passing any arguments.
/// !!WARNING!!: This has security risks. Do not run this on code you do not trust!
///

/// json with dates to parse
///
///
return JSON.stringify(json, FunctionReplacer);
};
JSON.stringifyAll = function(json) {
///
/// parses a JSON string and evaluates functions without passing any arguments also encodes
/// the date to a json compatible date format for the service.
/// !!WARNING!!: This has security risks. Do not run this on code you do not trust!
///
///

/// json with dates to parse
///
///
return JSON.stringify(json, AllReplacer);
};
JSON.jsonDateStringToDate = function(dtString) {
///
/// Converts a JSON ISO or MSAJAX string into a date object
///

/// Date String
///
var a = reISO.exec(dtString);
if (a)
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
a = reMsAjax.exec(dtString);
if (a) {
//var b = a[1].split(/[-,.]/);
//return new Date(+b[0]);
return new Date(eval(a[1]));
}
return null;
};

//tim c: created this to combine various replacers that I may come up with, so all can just call this one, and it forwards to the appropriates replacer
function AllReplacer(key, value) {
if (typeof value == "string") {
return DateReplacer(key, value);
} else if (typeof value == "function") {
return FunctionReplacer(key, value);
}
return value;
}

function DateReplacer(key, value) {
if (typeof value == "string") {
var a = reISO.exec(value);
if (a) {
// SKY: the '-0000' forces the serverside serializer into utc mode resulting in accurate dates.
var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + '-0000)/';

//SKY: rick, I don't think that asking for a JSON representation
// of an object should modify the state of the object. my 2 pesos.

//this[key] = val;

// this is the value that SHOULD be getting serialized but in the
// FF native JSON is ignoring this and serializing the member so I am guessing
// that is why rick is modifying the input object. gets the serialization job
// done properly but is a rather nasty undocumented side effect, in my opinion.
// I think it is probably better to overwrite native JSON with json2.js and
// get consistant results across platforms with out the need to modify the input
// UNLESS of course you are never going to need to use any of the objects that you
// serialize...
return val;
}
}
return value;
}

function FunctionReplacer(key, value) {
if (typeof value == "function") {
return value();
}
return value;
}
}
})();