This one's quite common. You asynchronously communicate (a.k.a. Ajax) with your server and get JSON data in return. You most probably use $.parseJSON()
on the client side which actually parses JSON string and returns an object. We've all used that. But (yes, there's always a but) there's a catch. There's no defined standard for date serialization, so jQuery will not parse your dates back to dates. What the heck even native JSON parser (these days supported in all major browsers) won't do that. So you have to do it yourself. Manually. Or, modify default jQuery functionality a bit and get it done automatically.
Browsers' native JSON support
Nowadays major browsers (Firefox, Internet Explorer and Google Chrome) natively support JSON serialization and deserialization which comes in two methods, one for each operation:
- object serialization to JSON string:
window.JSON.stringify(object data, [optional] function|array replacer, [optional] string space)
- JSON string deserialization to object:
window.JSON.parse(string data, [optional] function reviver)
- Internet Explorer natively supports JSON since version 8 including optional parameters usage,
- Firefox natively supports JSON since version 3.5 but it wasn't until version 3.5.4 that it supports optional parameters,
- I can't seem to find any valuable information about Google Chrome (haven't invested too much time to this either - anyone with any information drop me a comment); I've tested my latest version (which is 8.0.552.224 as of today) and it supports JSON as well as optional parameters.
No Date
support in JSON specification
JSON specification doesn't support Date
data type. That's why dates will not get auto-converted to their actual dates when parsing JSON strings back to actual objects. Lack of Date
data type support in JSON specification results in one single problem: How should dates be serialized? Since there's not standard, each framework thinks is smarter than the other and serializes them in their own way.
If we just use native browser JSON support all tree major browsers create a similar string following ISO 8601 standard. Internet Explorer and Firefox don't include milliseconds in the string, Chrome does. But nevertheless they are all consistent when it comes to serializing dates. This is how serialized dates look like in Internet Explorer and Firefox:
"2010-12-27T10-57-06Z"
or in case of Chrome which includes milliseconds
"2010-12-27T10-57-06.736Z"
But even though when dates are in this format using window.JSON.parse()
function will not automatically convert them back to actual dates. So doing something like this:
1: var str = window.JSON.stringify({myDate:new Date()});
2: // str == "{"myDate":"2010-12-27T11:59:18.119Z"}"
3: var obj = window.JSON.parse(str);
4: // obj.myDate == "2010-12-27T11:59:18.119Z"
Date
data type in JSON specification. Hence parser doesn't really know what a certain string represents other than a string.
Asp.net JSON serialization (including Asp.net MVC)
As mentioned browsers are quite consistent when it comes to date serialization. The same can't be said about server-side libraries. At least not Microsoft's Asp.net that uses one of the two serialization classes: JavaScriptSerializer
or DataContractJsonSerializer
that's used with WCF services. But they both produce strings like
"\/Date(1291244400000)\/"
which can be used in Javascript to create Date objects, but the main idea being that this string is completely different to how native JSON serializers in browsers serialize dates.
It's even more ridiculous that this kind of date serialization is not understood by Asp.net MVC default data binder when data gets back to the server. You will get a runtime error if you try to pass this kind of string to your action method with DateTime
parameter. Bad luck. Interestingly enough it does understand default browser serializations namely ISO standard dates. The main reason is that Asp.net MVC doesn't use JavaScriptSerializer
for data deserialization. Basically it doesn't support JSON data binding until version 3 where you have to register a special value provider factory for it to start working.
jQuery library and JSON support
jQuery doesn't support JSON serialization but it supports string deserialization back to objects by using $.parseJSON(string data)
which abstracts away the knowledge whether your browser has native JSON support or not. jQuery's function will reliably convert JSON strings to objects. But as you may've suspected it doesn't convert dates either. Function itself is quite smart because it uses native JSON functionality if it exists (resulting in much faster deserialization) or does its own parsing when native support isn't present. And it doesn't use eval()
either.
jQuery reusable extension that supports auto date conversion
jQuery library only uses basic native JSON browser functionality (when it exists) so it doesn't provide any special conversion techniques which can easily be used with dates parsing. The main idea here is that I extended default jQuery's $.parseJSON()
functionality by a bit more sophisticated function that can auto-convert dates when we want it to. My extension is therefore completely backwards compatible with existing code, but when you provide the additional boolean parameter and set its value to true
your dates in JSON strings (either Asp.net dates or ISO dates) will get converted to actual dates. The good thing's also that it doesn't traverse resulting objects but rather converts dates on the go. And it reliably works in browsers that have extended native JSON support or those that don't. So it really can be reused for any project. Just include this code in your client scripts.
1: /*!
2: * jQuery.parseJSON() extension (supports ISO & Asp.net date conversion)
3: *
4: * Version 1.0 (13 Jan 2011)
5: *
6: * Copyright (c) 2011 Robert Koritnik
7: * Licensed under the terms of the MIT license
8: * http://www.opensource.org/licenses/mit-license.php
9: */
10: (function ($) {
11:
12: // JSON RegExp
13: var rvalidchars = /^[\],:{}\s]*$/;
14: var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
15: var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
16: var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
17: var dateISO = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.,]\d+)?Z/i;
18: var dateNet = /\/Date\((\d+)(?:-\d+)?\)\//i;
19:
20: // replacer RegExp
21: var replaceISO = /"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:[.,](\d+))?Z"/i;
22: var replaceNet = /"\\\/Date\((\d+)(?:-\d+)?\)\\\/"/i;
23:
24: // determine JSON native support
25: var nativeJSON = (window.JSON && window.JSON.parse) ? true : false;
26: var extendedJSON = nativeJSON && window.JSON.parse('{"x":9}', function(k,v){return "Y";}) === "Y";
27:
28: var jsonDateConverter = function(key, value) {
29: if (typeof(value) === "string")
30: {
31: if (dateISO.test(value))
32: {
33: return new Date(value);
34: }
35: if (dateNet.test(value))
36: {
37: return new Date(parseInt(dateNet.exec(value)[1], 10));
38: }
39: }
40: return value;
41: };
42:
43: $.extend({
44: parseJSON: function(data, convertDates) {
45: /// <summary>Takes a well-formed JSON string and returns the resulting JavaScript object.</summary>
46: /// <param name="data" type="String">The JSON string to parse.</param>
47: /// <param name="convertDates" optional="true" type="Boolean">Set to true when you want ISO/Asp.net dates to be auto-converted to dates.</param>
48:
49: if (typeof data !== "string" || !data) {
50: return null;
51: }
52:
53: // Make sure leading/trailing whitespace is removed (IE can't handle it)
54: data = $.trim(data);
55:
56: // Make sure the incoming data is actual JSON
57: // Logic borrowed from http://json.org/json2.js
58: if (rvalidchars.test(data
59: .replace(rvalidescape, "@")
60: .replace(rvalidtokens, "]")
61: .replace(rvalidbraces, "")))
62: {
63: // Try to use the native JSON parser
64: if (extendedJSON || (nativeJSON && convertDates !== true))
65: {
66: return window.JSON.parse(data, convertDates === true ? jsonDateConverter : undefined);
67: }
68: else
69: {
70: data = convertDates === true ?
71: data.replace(replaceISO, "new Date(parseInt('$1',10),parseInt('$2',10)-1,parseInt('$3',10),parseInt('$4',10),parseInt('$5',10),parseInt('$6',10),(function(s){return parseInt(s,10)||0;})('$7'))")
72: .replace(replaceNet, "new Date($1)") :
73: data;
74: return (new Function("return " + data))();
75: }
76: } else
77: {
78: $.error("Invalid JSON: " + data);
79: }
80: }
81: });
82: })(jQuery);
Any questions, bugs, comments?
Let me know if you have any information about Google Chrome native and extended JSON support. If you have any further questions or would like to report a bug or suggest additional code improvements, you're more than welcome to drop me a comment.
I have a URL that returns JSON, including a JSON date (ie. "date_created":"\/Date(1293595692063-0700)\/"). I want to convert that date to a human-readable date *and* convert that with rest of the JSON to CSV for import into MS Access 2010. I'm a DB programmer and not much of a web developer so I am not sure how to get that JSON data into my DB. And pointers, ideas? Thx.
ReplyDelete@Anonymous: How do you get that JSON? Are you writing a web application and you
ReplyDelete(1) get JSON using Ajax on the client side?
(2) make server side request in code to some resource and get it that way?
Which server side technology are you using? Asp.net or something else?
Anyway.
If you get it by means of (1) then you can use jQuery with my extension and these date strings will get converted to actual dates automatically.
If you get it by means of (2), you will have to use JavaScriptSerializer class and pass it your date string and it will convert it. Or do the conversion yourself by using regular expressions. You can of course initialize a new DateTime instance by new DateTime(ticks);
Getting your data to a CSV is a different story. In case of .net you can check this code out on Stackoverflow http://stackoverflow.com/questions/1179816 that has an implementation of creating a CSV string from a collection of objects (enumeration actually).
And if you'd like to send generated CSV to browser client you shouldn't forget to set content MIME type to "text/csv". Check another Stackoverflow question about it: http://stackoverflow.com/questions/393647.
By the scarce info you provided I hope I helped you at least a bit.
Iget the JSON via a web service that I subscribe to. By going to a particular URL that was provided to me, I get JSON data for my account (analytics/statistics) sent back to my web browser. I am somewhat technical and could download Visual Studio Express if I needed to and plug away at it. Thanks for the links to the CSV conversion, that will come in handy. I wonder if that handles converting the dates to MM/DD/YYYY..... format??? That is what I am really needing more than anything. I need to store those dates in Access properly.
ReplyDeleteAnonymous:
ReplyDeleteI'm somehow confused. Do you make requests by manually writing a URL in the address box of a browser? Or do you make request using Javascript or server-side code? I suppose you just get data manually and you'd like to put it in an Access table...
Anyway. In code getting any date representation is rather easy. When you convert date serializations in actual DateTime instances you can format dates to whatever format you'd like... Yours would be (in C#):
someDate.ToString("MM/dd/yyyy");
Hi Robert, I am sorry I have confused you. I get the JSON by going to a specific URL at Godaddy.com and it returns statistics regarding my account there. It returns them as JSON inside the browswer.
ReplyDeleteI'd like to automate this process by creating some sort of javascript to go out to the URL, grab the JSON results *and convert that date to MM/DD/YYYY HH:MM:SS* in the process, and finally create a CSV file for import. The data changes daily (its statistics for domain names that I own) and so I'd like to dump that into an Access DB for historical purposes.
I have got that part working, the CSV part but its a real kludge as you might imagine. I run a batch file of CURL requests, changing the URL's page parameter each time through until there are no more pages to retrieve. Each loop creates a text file of the JSON data. At the end of that I concatenate the files and then run it through a JSON to CSV conversion routine I found. In my Access code I perform the update, deletes, inserts based on comparing the new records to the existing.
The problem is really getting that date converted to a normal date. Right now its stored as text. If I can make all these requests via javascript and automate that + the date conversion then cool!
Anthony
I guess the best place to convert dates to your liking is in your JSON to CSV conversion routine. But you may have to check language spec of that routine how to use regular expressions or similar.
ReplyDeleteI suggest you ask a concrete question (including the language of that routine and relevant example code) at Stackoverflow. It will be better for you because you will get much better answer than our comment conversation allows us to here.
OK, I will formulate a good question over there. Thank you for all of your help!! You did provide me some help! Happy New Year to you!!! Best wishes for 2011 and beyond.
ReplyDeleteAnthony
my json is coming like this /Date(12992148000000)/ and it throws Invalid JSON...could you help, it is an Jquery ajax call to a ASP.NET web service thanks
ReplyDelete@Anonymous: String "/Date(12992148000000)/" is of course invalid JSON. What should it represent? Ok. it represents Date instance. But what should it do with it? usually Asp.net returns an actual JSON objects as
ReplyDelete'{"d":"/Date(12992148000000)/"}'
Parsing this one would be ok. So the problem is not your invalid Date string representation, but JSON object. This string per-se can't be used at all? Where should it be stored?
I suggest you read about JSON format a bit on jQuery and a bit on JSON.org. Right the first object definition on JSON shown you how objects are defined... They should contain string literal along with their value.
I understand that, thanks.. I am returning a list which is defined with a public field on C# public DateTime date{get;set;} by the web service. this comes along with 3 other fields declared like so. on the callback in jQuery I am doing
ReplyDeletefunction OnSuccessEntries(data, status) {
var items = data.d;
so var is the collection of List items returned. by doing $.each(items, function (index, item) {i am trying to parse the date like
jQuery.parseJSON(item.date)}
This is obviously wrong could you let me know how to pass that json to the parseJSON() you provide....
thanks
I fixed a bug relating to converting dates in JSON array in IE7 (no native JSON parser).
ReplyDeleteAdded “g” to replaceISO and replaceNet to enable global replace, not only the first one :-).
var replaceISO = /"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:[.,](\d+))?Z"/gi;
var replaceNet = /"\\\/Date\((\d+)(?:-\d+)?\)\\\/"/gi;
Thank you very much for your code contribution. Ivan
I think its useful to add example of real usage of such function. I was able to it in such way:
ReplyDelete$.ajax({
type: "GET",
dataType: 'json',
url: 'url',
converters: {
"text json": function(data) {
return $.parseJSON(data, true);
}
},
success: function(data) {
});
Had to spent some time to figure that out
@Sly: I'm sorry but I though it was pretty obvious from the code XML documentation where both parameters are very well documented and also in such a way that they show up in Visual Studio's intellisense.
ReplyDeleteCode documentation is as follows:
/// <summary>Takes a well-formed JSON string and returns the resulting JavaScript object.</summary>
/// <param name="data" type="String">The JSON string to parse.</param>
/// <param name="convertDates" optional="true" type="Boolean">Set to true when you want ISO/Asp.net dates to be auto-converted to dates.</param>
I used this like so:
ReplyDelete$.getJSON('ajax/SelectSession.aspx', { id: getid }, function (data) {
$.parseJSON(data, true);
$("#dt1label").html(data.StartTime);
$("#dialog-session").dialog("open");
});
But the asp.net DateTime is still returned as /Date(1325595600000+0000)/
@Richard Allwood: That's because you're using getJSON that already parses the string into a Javascript object.
ReplyDeleteYou have two options that I can immediately think of:
1. use $.ajax instead that will return string that you can parse yourself using parseJSON with the additional parameter
2. modify parseJSON so it will convert dates by default; I'm using such a modification myself because dates always get scrambled from the Asp.net side so I always want them as dates and never as strings...
How to do the second one? Just add this code to line 48:
convertDates = convertDates === false ? false : true;
and you should be fine with getJSON because dates will always get converted unless explicitly instructed not to...
Hope this helps.
I think you forget to include g modifier in your replaceISO and replaceNet patterns. For now it only replace the first occurrence of the date in given string.
ReplyDeleteHi Robert,
ReplyDeleteI've included your script, I've added the line 48 modification (to parse dates by default), yet when I call
$.get('/url', {}).success(function(o) { // expecting o.BirthDate to be a Date });
your script does not get executed. What am I missing? I'd like your script to be executed by default on all JSON responses.
Interestingly, this works:
Delete$.ajax({
url: '/url',
converters: {
'text json': $.parseJSON
}
})
What's weird is that converter for 'text json' is supposed to be $.parseJSON by default.
OK, so apparently the default converter references jQuery's own $.parseJSON method. Once we "override" it with your script, we need to reset the converter. We can do it globally with $.ajaxSetup:
Delete$.ajaxSetup({
converters: {
'text json': $.parseJSON
}
});
By the way, Robert, thanks a bunch for sharing the code. It's fantastic work, and I'm very grateful!
DeleteFinally, any idea on how we could do the exact opposite, globally?
DeleteI.e. when I have a javascript Date object that I want to send as the data parameter in an AJAX request, how can I send it as a string?
var date = new Date();
$.get('/url', { myDate: date });
Dear developer,
DeleteYou've done major part yourself already. And you know what? I have a solution for your last problem as well. If you read my other post (and use the code), you will be able to do this:
var date = new Date();
$.get('/url', $.toDictionary({ myDate: date }));
That will convert your date appropriatelly so Asp.net MVC will parse it as expected.
Best regards, Robert
Hi Robert,
DeleteThat's a great script for addressing model binding issues between ASP.NET MVC and jQuery... and it solves the Date issue (previously, I was simply using the date.format.js library).
Question is, is there a way to do this processing automatically and globally, without having to manually call $.toDictionary during each AJAX request? I've been looking for some kind of hook into jQuery's AJAX functionality... but Date values are already stripped (and forever lost) by the time prefilters or beforeSend is called, even if you set processData option to false.
Best,
Ara
Hello! Sorry, I don't understand completely why need I using $.ajaxSetup() to reset converter. Doesn't our custom parseJSON override jQuery's parseJSON?
DeleteHello,
ReplyDeleteI ran into a slight problem with this as the date I was using was \/Date(1296996475643+0000)\/ (with a "+" rather than "-"), so I changed the dateNet regex to /\/Date\((\d+)(?:[-\+]\d+)?\)\//i;
Also - I'm not so keen on overriding the jQuery parseJSON functionality, so I decided to use it as a converter that you can pass the response through with $.getJSON().pipe($.convertJSONDates) , see https://gist.github.com/1753574
There is a chance that a malformed JSONP response could end up with infinite recursion - though I think I like this approach more.
Ben
Hi, Robert, this is a useful plugin.
ReplyDeleteThe regex for matching dates currently only matches UTC dates, because the terminating "Z" is not optional.
I'd change it to the following:
var dateISO = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.,]\d+)?Z?/i;
Thanks!
Thanks Noel for the comment. I decided in the meantime to put this jQuery extension on GitHub so it will be easier to access it. I haven't transferred the code there but have already created an issue ticket related to this.
DeleteI've checked ISO 8601 standard a bit more thoroughly and it seems I will have to change my regular expression a bit more than just appending the missing Z.
Thanks again for your comment.
Hi Robert - I'm using your sweet jQuery extension to convert epoch times created by the .NET Json.Encode() method into Javascript date/time types. It works great, but I'd like to keep the times in UTC instead of having them automatically converted to the user's system time. Do you have any suggestions for me? Thanks.
ReplyDeleteI think that+s how Javascript engine treats dates. By UTC you mean you'd like to display time by adjusting it to UTC+00?
DeleteHello Robert,
ReplyDeletethanks for the extension.
I've found a bug - it fails to parse .NET serialized dates when the date is lesser than 1970/01/01
Modifying the regular expressions below fixes that:
var dateNet = /\/Date\((-?\d+)(?:-\d+)?\)\//i;
var replaceNet = /"\\\/Date\((-?\d+)(?:-\d+)?\)\\\/"/ig;
Absolute lifesaver. Thank you SO much.
ReplyDeleteGlad to help. :) And always glad to hear others have liked it as well.
DeleteA Simple solution is this:
ReplyDeletevar startDate = new Date(parseInt(jsondate.substr(6)));
startDate = lastModified.toString("MMM d, yyyy h:mm tt");
It is yes, but it has several downsides: you will have to duplicate all code related to dates (while JSON parsing stays) including several per JSON object nested within it, and it will only work with Asp.net date format.
DeleteIt is of course your own preference what you'd like to use. I just think it's better to automate this and not write a single line of custom code related to date conversion.
Hi,
ReplyDeleteI have adder you code on my page as :
lt;script type="text/javascript"gt;
$(document).ready(function () {
(function ($) {
// JSON RegExp
var rvalidchars = /^[\],:{}\s]*$/;
.....
lt;/scriptgt;
& my ajax call is like
$.ajax({
type: serviceType, //GET or POST or PUT or DELETE verb
url: serviceUri, // Location of the service
data: serviceData, //Data sent to server
contentType: serviceContentType, // content type sent to server
dataType: serviceDataType, //Expected data format from server
processdata: processData, //True or False
async: (isAsync === null ? true : isAsync),
converters: {
"text json": function (msg) {
return $.parseJSON(msg, true);
}
},
success: function (msg) {
//On Successful service call
eval(onSuccessFunction + "(" + msg + ")");
},
error: ServiceFailed// When Service call fails
});
but its not working... can you tell me whats wrong
Very nice explanation!
ReplyDeleteThank you for making things clearer!
Robert,
ReplyDeleteThis is exactly what I'm looking for, unfortunately it's not working for me. I am getting an 'Invalid character' error. The date I'm passing in is formatted like "/Date(1372960800000-0500)/" and I'm calling the function like "var date = $.parseJSON(jsonDate, true);" If you could provide any guidance it would greatly appreciated.
Thanks so much!
I can see that Invalid character is being thrown by window.JSON.parse in IE because Chrome and Firefox report a different error.
DeleteThe problem you're having is that just this particular .net provided date string isn't valid JSON. It has to be at least a value of some sort i.e. object property value or at least array element:
jsonDate = '["/Date(1372960800000-0500)/"]';
This would parse it as exected although you'd need to access it as date[0] when used in this way as date is now an array with a single value.
You can check JSON validity on JSON Lint.
Hope this helps.
Thank you, just what the doctor ordered! My problem was I used JSON.stringify() to store an object that included dates in localStorage, but when I brought it back with $.parseJSON(), the dates were wrong. Took me a while to figure out where the problem is, but once I got it, it was easy to find and implement you solution.
ReplyDeleteGreat post Robert!
ReplyDeleteI had headache with converting dates returned from webapi. Then found your post that pointed me to right direction.
Also found one other solution with native JSON object (http://www.json.org/js.html).
In parse function you can also pass own function for parsing:
http://msdn.microsoft.com/en-us/library/ie/cc836466(v=vs.94).aspx
This function fails on IE8. Due to IE 8 not supporting "2010-12-27T11:59:18.119Z" dates.
ReplyDeleteI added a fromISO prototype (from a stackoverflow message)
(function () {
var D = new Date('2011-06-02T09:34:29+02:00');
if (!D || +D !== 1307000069000) {
Date.fromISO = function (s) {
var day, tz,
rx = /^(\d{4}\-\d\d\-\d\d([tT ][\d:\.]*)?)([zZ]|([+\-])(\d\d):(\d\d))?$/,
p = rx.exec(s) || [];
if (p[1]) {
day = p[1].split(/\D/);
for (var i = 0, L = day.length; i < L; i++) {
day[i] = parseInt(day[i], 10) || 0;
};
day[1] -= 1;
day = new Date(Date.UTC.apply(Date, day));
if (!day.getDate()) return NaN;
if (p[5]) {
tz = (parseInt(p[5], 10) * 60);
if (p[6]) tz += parseInt(p[6], 10);
if (p[4] == '+') tz *= -1;
if (tz) day.setUTCMinutes(day.getUTCMinutes() + tz);
}
return day;
}
return NaN;
}
}
else {
Date.fromISO = function (s) {
return new Date(s);
}
}
})();
And updated the jsonDateConverter function to
var jsonDateConverter = function (key, value) {
if (typeof (value) === "string") {
if (dateISO.test(value)) {
return new Date.fromISO(value);
}
if (dateNet.test(value)) {
return new Date(parseInt(dateNet.exec(value)[1], 10));
}
}
return value;
};
It might help you!
ReplyDeleteConvert json date to date format in jQuery
http://www.code-sample.com/2015/06/convert-json-date-to-date-format-in.html
Hello, Robert! If you are working on l10n projects, I may suggest to have a look at POEditor which is a good online localization tool created to automate and improve the workflow.
ReplyDeleteI'm using POEditor on my current project so yes, I know about it. I'm more asking myslef whether this comment of yours is spam or not as it doesn't have anything specifically to do with ISO/.net JSON dates. PO is just translation stuff of everyday strings.
DeleteSo. Should I mark your comment as spam or keep it as legit? :) It's true it doesn't have harmful links though.