How can I speed up this bit of JSON date parsing? - javascript

I am stuck using an AJAX library from about 5 years ago in this project, and it had some issues with parsing dates in JSON. I wound up rewriting its parse function to use a single regex:
return eval('(' + (enableDateParsing ? text.replace(/"(?:\\)?\/Date\((.*?)\)(?:\\)?\/"/g, "new Date($1)") : text) + ')');
This works really well, but I thought I could get a speed up if I used native JSON parsing in IE8 / chrome / ff, so I added this bit:
if (typeof JSON !== 'undefined' && typeof JSON.parse !== 'undefined') {
var nativeJsonDateParseRegex = /\/Date\(.*?\)\//g;
return JSON.parse(text, function (key, value) {
if (AjaxPro.enableDateParsing && typeof value === 'string' && value.match(nativeJsonDateParseRegex))
{
value = new Date(parseInt(value.substr(6)));
}
return value;
});
}
else // revert to eval for ie6/ie7
The reviver callback will execute once for each JSON property returned, so it has to be very fast. During a profile I've seen it's been called 170484 times, but still runs pretty fast (131.237ms). Any ideas on how to make it faster, or is this the best you can do without serious tweaking?

Your code contains a lot of constant conditions, you'll be fine with checking once whether native JSON is supported or not.
Suggestions:
check for native JSPN support at page load, and add the right function accordingly.
Drop the global flag from the regex if you do not need it
Drop regular expressions if possible, if every date always starts with "/Date(", search for it. It's much faster (see benchmark at jsperf.com)
todo: check whether parseInt can be replaced with an other method to get rid of the trailing )/.
If AjaxPro.enableDateParsing is a constant, you can remove if from AjaxPro.jsonParse and and make it a condition like the check for native JSON
Code without RE:
if (typeof JSON !== 'undefined' && typeof JSON.parse !== 'undefined') {
AjaxPro.nativeJsonDateParseRegex = /\/Date\(.*?\)\//g;
AjaxPro.dateFunc = function(key, value) {
if (typeof value === "string" && !value.indexOf("/Date(")) {
return new Date(value.substring(6, value.length-2));
}
return value;
};
AjaxPro.jsonParse = function(text) {
if (AjaxPro.enableDateParsing) {
return JSON.parse(text, AjaxPro.dateFunc);
}
return JSON.parse(text);
};
} else // revert to eval for ie6/ie7
This should be highly optimized. You might want to run some more test on your own in multiple browsers. Maybe checking for a property of a string is faster than checking its type (doubt it), thing like that.

One not so good microoptimization, but still worth giving a try.
Since your substring contains millisecond timestamp only, and no other garbage string.
You can remove the call to parseInt.
You can try typecasting with simple mathematical operation like multiplication with 1.
Might save some time if you are too keen on microoptimizations.
value = new Date(1*(value.substr(6)));
example:
a = "a:3333";
b = a.substring(2);
alert(b*2); // alerts 6666

Related

Check for array in JS

I'm trying to check a value in JS that on page load is returned as a single-element array and after an ajax function returns as a string. I don't know why it's doing this but I'm trying to role with it.
So, using console.log(value) I get array ['Scranton'] on page load, and the ajax even returns string "Scranton"
When trying to check this variable, this does not work as I intended:
if ( value === 'Scranton' || value === ['Scranton']){
...
}
Any help is appreciated!
This would probably work but I would try and fix the underlying issue instead of working around it.
if ( value === 'Scranton' || value[0] === 'Scranton'){
...
}
You can use indexOf for both an array and a string, so value.indexOf("Scranton") !== -1 will work (just tested this on the console).
However you must first check for null/false/undefined or it will error.
if (value && value.indexOf("Scranton") !== -1) {}
EDIT: As Felix said, this will also be true for any string containing "Scranton". If this is a problem, then you can check for indexOf == 0 instead, which will be true for any string starting with "Scranton". It really depends on your concrete problem if this solution fits you. Use with care.
if (Object.prototype.toString.call(value) === '[object Array]') {
if (value.indexOf('Scranton') != -1) {
/* */
}
} else {
if (value === 'Scranton') {
/* */
}
}
Edit 1:
First, you need to check if "value" is an array. If it's an array and contains the string "Scranton", you can find it using value.indexOf().
And if "value" is not an array, you can directly compare it with the string 'Scranton'.
What does "after an ajax function" mean? The page loads with a default variable that has been assigned an array value, and then an Ajax request is made, which changes this default variable, and instead of an array being assigned, it assigns a string? Assuming this "ajax function" changes the default variable to the response text from the server, there is your problem: Ajax--like any other request--is text-based, so it is a string. If you are responding to the Ajax request with a JSON string, built on the server, it needs to be parsed in the browser, so it can be reinterpreted as an array. See the JSON.parse method.

does object/array exist in javascript

I'm trying to put content from RSS feed - problem is every RSS feed has different formats for images, content etc.
I'm trying to see if certain object exists in javascript or jquery:
item.mediaGroups[0].contents[0].url
How can I check it in an if statement? I keep getting for feeds without this structure:
Uncaught TypeError: Cannot read property '0' of undefined
Also tried:
if (typeof item.mediaGroups[0].contents[0].url === "undefined")
but I keep getting the same error.
thanks!
There is no "simple" built in way to do this sort of in depth checking. The reasoning is simple - most of the time you know the type of the objects you're working against.
You can do:
if (typeof item !== "undefined" &&
typeof item.mediaGroups !== "undefined" &&
typeof item.mediaGroups[0] !== "undefined" &&
typeof item.megiaGroups[0].contents !== "undefined" &&
typeof item.megiaGroups[0].contents[0] !== "undefined" &&
typeof item.mediaGroups[0].contents[0].url !== "undefined"){
When you type all that you might want to consider your data structures, since this really is not a situation you should be in to begin with :)
(hint, you can skip the typeof on all but the first, but I think typeof is a good clarification here).
The real question is this:
Why are you not sure what the structure of your data is?
If you are querying data (for example XML in an RSS feed) there are effective ways to do so with XPATH or query selectors. Object property access is built for objects where what you're querying is a document. Sure, it's possible with a bunch of ugly checks just like you can hammer a nail in with a heavy screwdriver.
You can see this question in Stack Overflow on how to use DOM methods to parse XML.
If you're uncertain about the exisence of properties, try this helper function:
function getProperty(root) {
var l = arguments.length, i, undefined;
for( i=1; i<l; i++) {
if( typeof root[arguments[i]] == "undefined") return undefined;
root = root[arguments[i]];
}
return root;
}
You can then call it like this:
var url = getProperty(item,'mediaGroups',0,'contents',0,'url');
As a more "haxy" way, you can try this:
try {url = item.mediaGroups[0].contents[0].url;}
catch(e) {url = undefined;}
I would check the length of both arrays in this case to be sure - before assuming there are objects defined at index 0
item.mediaGroups.length > 0
and
item.mediaGroups[0].contents.length > 0
As the outer check you can also throw in a
if(item.mediaGroups){
}
How about 'optional chaining' (described in ES2021 spec and already implemented in all browsers except three) ?
from MDN:
The optional chaining operator provides a way to simplify accessing
values through connected objects when it's possible that a reference
or function may be undefined or null.
The optional chaining ?. stops the evaluation if the value before ?. is undefined or null and returns undefined so it is giving us a way to handle the possibly undefined/nullsish values
item?.mediaGroups[0]?.contents[0]?.url // will evaluates to undefined if either of those is undefined.
item.mediaGroups[0].contents is undefined, you have to check for it.
if(item.mediaGroups && item.mediaGroups[0].contents) {
return item.mediaGroups[0].contents[0].url;
}
It's not a solution with if-statements (as requested), but you can use exceptions to achieve similar functionality. Something like this:
function throwOrReturn(thing){
if(typeof thing === 'undefined'){
throw "Didn't find it..."
}else{
return thing
}
}
// The unknown thing.
var a = {
b1: {
},
b2: {
c: 'lookingFor'
}
}
var c
// Test our different paths.
try{
// First guess.
c = throwOrReturn(a.b1.c.d)+" - a.b1.c.d"
}catch(error){
try{
// Second guess.
c = throwOrReturn(a.b[45][34].c)+" - a.b[45][34].c"
}catch(error){
try{
// Third guess.
c = throwOrReturn(a.b2.c)+" - a.b2.c"
}catch(error){
// Try more guesses, or give up.
c = "notFound"
}
}
}
console.log("c:", c) // Logs: "c: lookingFor - a.b2.c"
It ain't pretty, but it's an alternative worth to mention.

Why is angular.isNumber() not working as expected?

It appears as if AngularJS's angular.isNumber is not working. It doesn't work with strings that are numbers. Am I doing something wrong? Should I just use isNaN()?
angular.isNumber('95.55') == false
angular.isNumber('95.55' * 1) == true
angular.isNumber('bla' * 1) == true
angular.isNumber(NaN) == true
I need something to see if a string is a number (when it actually is) and angular.isNumber() won't let me do that unless I multiply by 1, but if I do that then it will always be true. Also NaN is not a number (by definition) and so should return false.
In JavaScript, typeof NaN === 'number'.
If you need to recognise a String as a Number, cast it to Number, convert back to String and compare this against the input, for example.
function stringIsNumber(s) {
var x = +s; // made cast obvious for demonstration
return x.toString() === s;
}
stringIsNumber('95.55'); // true
stringIsNumber('foo'); // false
// still have
stringIsNumber('NaN'); // true
I was working on the same problem and I was trying to work around that edge case. So I created a slightly different approach.
FIDDLE
function isStringNumber(str) {
var parsed = parseFloat(str);
var casted = +str;
return parsed === casted && !isNaN(parsed) && !isNaN(casted);
}
Use it as below,
angular.isNumber(eval('99.55'))
for other expressions also we may use eval(input).
Note: eval() is a javascript method
Edit:
It is not recommended to use eval(), as document says Never use eval()!
Thanks #Diogo Kollross

How to prevent automatic JSON key type-casting

Good day to you all,
I've encountered a frustrating issue that seems to happens only in Chrome.
var response = '{"01":"January","02":"February"}',
months = JSON.parse(response);
console.log(months['02']) // undefined in Chrome (my version is 24.0.1312.5 beta)
console.log(months[2]) // "February"
Firefox and Safari seem to handle this as expected, whereas Chrome is casting string-ish JSON keys to integers.
jQuery's parseJSON method has the same behaviour (I'm assuming it relies on the browser's JSON.parse method).
I'm fairly tied down to this specific API response format, so I'd rather not change the server's response. Is there a sane way to force Chrome to behave as expected?
"Is there a sane way to force Chrome to behave as expected?"
Not sure if you call this sane, but you can do some manipulation in a reviver function to patch it.
var response = '{"01":"January","02":"February"}',
months = JSON.parse(response,
function(k,v) {
if (this.constructor === Object && // is Object
!isNaN(k) && // key is a Number
+k > 0 && // from 1
+k < 1 && // to 9
k.charAt(0) !== '0') { // missing the '0'
this['0' + k] = v; // manually assign the key
return; // return undefined to prevent assignment
}
return v; // allow the assignment
});
console.log(months['02']);
Of course you'll likely need to tweak it a bit for your code so that you're not fixing things that don't need to be fixed.
You'll probably also want to test the browser before it runs to see if the fix is needed.
var needsJSONfix = !JSON.parse('{"01":1}')["01"];
months = JSON.parse(response, needsJSONfix ? fixFunc : null);

Trouble understanding jQuery.parseJSON JSON.parse fallback

This is the source of $.parseJSON
function (data) {
if (typeof data !== "string" || !data) {
return null;
}
// Make sure leading/trailing whitespace is removed (IE can't handle it)
data = jQuery.trim(data);
// Attempt to parse using the native JSON parser first
if (window.JSON && window.JSON.parse) {
return window.JSON.parse(data);
}
// Make sure the incoming data is actual JSON
// Logic borrowed from http://json.org/json2.js
if (rvalidchars.test(data.replace(rvalidescape, "#").replace(rvalidtokens, "]").replace(rvalidbraces, ""))) {
return (new Function("return " + data))();
}
jQuery.error("Invalid JSON: " + data);
}
I have trouble understanding the following fallbacks
return (new Function("return " + data))();
and also ( this one is not in jQuery )
return (eval('('+ data + ')')
I would like to know these things
How this parsing fallback works really?
Why eval is not used in the fallback? (Is it not faster than new Function())
new Function() allows you to pass your function as a string.
In this case, the function is created to simply return the object described by the json string. Since the json is a valid object literal, this function simply returns the object defined in the json. The new function is immediately invoked, returning that object.
As far as performance, some quick googling found claims that new Function() is faster than eval, though I have not tested this myself.

Categories

Resources