How to use JSON.parse reviver parameter to parse date string
My JSON string contains a date field that returns such a value:
"2009-04-04T22:55:16.0000000-04:00"
I am particularly interested in parsing only the date compartment not the time. I tried using a reviver function, but interestingly the reviver function is never invoked! (tried on Firefox)
Here is my code to accomplish that:
var Site = {
.....
dateReviver: function(key, value) {
var a;
if (typeof value === 'string') {
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
},
loadArticle: function(id) {
....
proxy.getArticle(id, functi开发者_如何学编程on(response) {
var data = JSON.parse(response.result, Site.dateReviver);
....
});
}
};
JSON.parse in loadArticle
never calls dateReviver
.
I invested a whole day but no luck! Could someone please help me?
Using TypeScript, my solution is as follows:
export function parseWithDate(jsonString: string): any {
var reDateDetect = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/; // startswith: 2015-04-29T22:06:55
var resultObject = JSON.parse(jsonString,(key: any, value: any) => {
if (typeof value == 'string' && (reDateDetect.exec(value))) {
return new Date(value);
}
return value;
});
return resultObject;
}
Best of all worlds ;-) It uses an anonymous datereviver, which gets called by JSON.parse on each property. The reviver logic is to check whether the property is of type string and if so, whether it looks like the start of a date ... If it is a date, then let new Date(value) do the actual parsing ... all timezone variations are supported that way.
Hope it helps!
The regular expression expects a "Zulu" timezone (A 'Z' character at the end), while the sample date-time string shows a numeric timezone ('-04:00'). The following regex will accept both:
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/
If the time zone digits are not zero, you might want to actually modify the date after parsing and/or converting to UTC, to respect the timezone.
I can see dateReviver() being hit. Try the following in a browser:
<!-- saved from url=(0014)about:internet --> <html> <head> <script src="http://www.json.org/json2.js"></script> <script type="text/javascript" src="http://ajax.Microsoft.com/ajax/jQuery/jquery-1.3.2.js"></script> <script> $(function () { // a mock proxy to return some json to play with var proxy = { getArticle: function(id, foo) { foo({ result: '["2009-04-04T22:55:16.0000000-04:00"]' }); } }; // the origial Site object, with the fixed regex var Site = { dateReviver: function(key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }, loadArticle: function(id) { proxy.getArticle(id, function(response) { var data = JSON.parse(response.result, Site.dateReviver); // put the parsed JSON date on the page $("#output").html(data[0].toString()); }); } }; // try out our Site object Site.loadArticle(); }); </script> </head> <body> <div id="output"></div> </body> </html>
I am getting the following in the browser, indicating successful parsing:
Sat Apr 4 15:55:16 PDT 2009
Extending the jQuery.ajax converters setting worked fine for me from its's default:
"text json": jQuery.parseJSON
to
"text json": function (xmlValue) {
var value = JSON.parse(xmlValue, Site.dateReviver);
return value;
}
The use of return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
does not adjust the date for the timezone information, the -4:00
in the example.
An alternative is to let Date() do the parsing for you:
var dateReviver = function (key, value) {
var a;
if (typeof value === 'string') {
a = Date.parse(value);
if (a) {
return new Date(a);
}
}
return value;
}
If the JSON had been formatted with JSON.stringify() it would have been in UTC (Z).
function dateReviver (k,v) {
var isnum = /^\d+$/.test(v);
// Check if number since Date.parse(number) returns valid date
if (isnum) {
return v;
}
if (Date.parse(v)) {
return new Date(Date.parse(v));
}
return v;
}
Drop-in solution
const jsonDateRegexp = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
function jsonRetriever(key: string, value: any) {
// let's try to detect input we dont have to parse early, so this function is as fast as possible
if (typeof value !== 'string') {
return value;
}
const dateMatch = jsonDateRegexp.exec(value);
if (!dateMatch) {
return value;
}
return new Date(
Date.UTC(
+dateMatch[1],
+dateMatch[2] - 1,
+dateMatch[3],
+dateMatch[4],
+dateMatch[5],
+dateMatch[6],
+dateMatch[7],
),
);
}
export function parseJsonWithDates(input: string) {
return JSON.parse(input, jsonRetriever);
}
I ran into this problem today and I have a different approach from those contributed by others.
If you know the keys that contain dates ahead of time, you could write the reviver like so:
export const jsonDateReviver = (keysToParse: Record<string, boolean>) => (key: string, value: any) => {
if (keysToParse[key]) {
if (Array.isArray(value)) {
return value.map(v => new Date(v));
}
return new Date(value);
}
return value;
};
You could then use it like so:
JSON.parse(data, jsonDateReviver({ lastModifiedAt: true }))
Or you could have a global keysToParse
object instead of redefining it every time
精彩评论