I am trying to deserialize a json object that has a javascript date in it. When JSON.stringify is called on the object, dates are serialized to strings that are not properl
I took @LastCoder advice and wrote a simple implementation. It seems to be doing what I wanted it to.
var jsonDates = {
dtrx2: /\d{4}-\d{2}-\d{2}/,
parse: function(obj){
var parsedObj = JSON.parse(obj);
return this.parseDates(parsedObj);
},
parseDates: function(obj){
// iterate properties
for(pName in obj){
// make sure the property is 'truthy'
if (obj[pName]){
var value = obj[pName];
// determine if the property is an array
if (Array.isArray(value)){
for(var ii = 0; ii < value.length; ii++){
this.parseDates(value[ii]);
}
}
// determine if the property is an object
else if (typeof(value) == "object"){
this.parseDates(value);
}
// determine if the property is a string containing a date
else if (typeof(value) == "string" && this.dtrx2.test(value)){
// parse and replace
obj[pName] = new Date(obj[pName]);
}
}
}
return obj;
}
};
A live example is available on jsbin. A reference is available on gist.
The JSON spec does not include special formatting for dates. As such they are often serialized as a string, sometimes with special markings to indicate it should be treated as a Date object if the language supports them. As such, most (all?) browser-native JSON parsers can not round-trip a Date object properly.
There are several good libraries that help with this - I very much like MomentJS though I have used datejs in the past as well. You would just need to iterate over your objects and convert the proper fields to Date objects after they have been parsed.
I find it helpful to remember that the JSON format is much more restrictive than JavaScript object literal notation.
In order to represent dates using JavaScript, I found that JSON uses ISO 8601, a specific string format to encode dates as string. When I last checked though, there is not an official standard for what the date format should look like. The major browsers use ISO 8601 as the JSON Date encoding format.
So, dates are encoded as ISO 8601 strings and then used just like a regular strings when the JSON is serialized and deserialized.
That being said, ISO dates can be converted into JavaScript dates by use of the JavaScript Date constructor, which accepts a wide variety of inputs to construct a date, ISO 8601 being one of them.
Get todays date:
var curDate = new Date();
document.write(curDate); //Mon Feb 01 2016 12:57:12 GMT-0600 (Central Standard Time)
Parse it into a string:
var dateStr = JSON.parse(JSON.stringify(curDate));
document.write(dateStr);//2016-02-01T18:59:35.375Z
Then convert it back to a javascript date, using the constructor:
var date = new Date(curDate);
document.write(date); //Mon Feb 01 2016 12:59:35 GMT-0600 (Central Standard Time)
You could manually add all of the Date functions you require to the String.prototype.
String.prototype.getYear = function() {
return Date.parse(this).getYear();
};
var obj = {date: new Date()};
var dtObj = JSON.parse(JSON.stringify(obj));
console.log(dtObj.date.getYear());
Or you could override JSON.parse and have it loop through the result object looking for strings that match the time stamp regex and then convert them to Date objects.
var JSON_parse = JSON.parse;
JSON.parse = function(str) {
var res = JSON_parse(str);
findAndConvertStringsToDates(res);
return res;
}
EDIT Here's what I'd throw together for an implementation
(function() {
var jsonParse = JSON.parse;
var reDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/i;
function jsonDate(obj) {
var type = typeof(obj);
if(type == 'object') {
for(var p in obj)
if(obj.hasOwnProperty(p))
obj[p] = jsonDate(obj[p]);
return obj;
} else if(type == 'string' && reDate.test(obj)) {
return new Date(obj);
}
return obj;
}
JSON.parse = function(str) { return jsonDate(jsonParse(str)); }
})();
/*
* Tests
*/
var dt = JSON.parse(JSON.stringify({date: new Date()}));
console.log(typeof(dt.date));
console.log(JSON.parse(JSON.stringify(null)));
console.log(JSON.parse(JSON.stringify(123)));
console.log(JSON.parse(JSON.stringify("test")));
console.log(JSON.parse(JSON.stringify(new Date())));
console.log(JSON.parse(JSON.stringify([1,new Date(),2])));
console.log(JSON.parse(JSON.stringify({d: new Date(), d2: {d3: new Date(), d4: [0,new Date(),4]}})));
JSON.parse
has a little-known second parameter: the 'reviver' function. This is used for precisely this purpose: to revive a date string into a Date
object (or, hypothetically, any other kind of object you wanted to convert from string) during the initial parse.
There's an SO post about this, and here's a blog post that includes an implementation example and a function that will do property checking for a couple common date encodings (ISO & that weird .NET AJAX format), before parsing to a Date
.
Here's the key function from that blog post, fwiw:
// JSON date deserializer
// use as the second, 'reviver' argument to JSON.parse();
if (window.JSON && !window.JSON.dateParser) {
var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
JSON.dateParser = function (key, value) {
// first, just make sure the property is a string:
if (typeof value === 'string') {
// then, use regex to see if it's an ISO-formatted string
var a = reISO.exec(value);
if (a) {
// if so, Date() can parse it:
return new Date(value);
}
// otherwise, see if it's a wacky Microsoft-format string:
a = reMsAjax.exec(value);
if (a) {
// and perform some jujitsu to make use of it:
var b = a[1].split(/[-+,.]/);
return new Date(b[0] ? +b[0] : 0 - +b[1]);
}
// here, you could insert any additional tests and parse instructions you like, for other date syntaxes...
}
// important: you need to return any values you're not parsing, or they die...
return value;
};
}
// use: JSON.parse(json,JSON.dateParser);
(There are lots of opinions about proper regexes for ISO 8601 dates. YMMV. Also, there's no particular reason to punch the function onto the global JSON object. You could store/reference it anywhere you like. )