Thursday 9 April 2009

JavaScript JSON Serialize

First of all this approach is not for everyone there are many fully implemented JSON libraries for JavaScript but i'll explain the scenario. I recently worked on an ASP.Net project which relied heavily on page methods and ASMX webservices.

We attempted to make the in page JavaScript as "light-weight" as possible by cutting down on using the ASP.Net ScriptManagers JavaScript proxys. This technique is really useful in a small project but when we have 50+ services which may be called by one page injecting that many js files cant be good for bandwidth or performance. Using the jQuery library as a foundation for the majority of the project we found using the ajax request methods difficult to use in conjunction with our web methods. After following Dave Ward's great article on connecting jQuery to such services we were left with the huge pain of converting our JavaScript objects into JSON serialized form.

The json.js file that ASP.Net generates when using the scriptmanager was out of the question since we wanted to avoid the Microsoft bloat so I wrote a custom JSON serializer for the client. This way we could build our objects in JavaScript then serialize them to a JSON string on the fly whilst still keeping the project as lightweight as we needed. So enough jibber jabba heres the code I hope its useful to somebody.


// Change the variable name if you like 'JSON' seems pretty general
var JSON = function() {

// Method to decide the variables type and call its relevant method
var getNameValue = function(name, value) {
var t = typeof value;
if (value === undefined) { return getValueUndefined(name, value); }
if (value === null) { return getNameValueNull(name, value) }
switch (t) {
case 'string':
return getNameValueString(name, value);
case 'number':
return getNameValueNumber(name, value);
case 'boolean':
return getNameValueBoolean(name, value);
case 'object':
// If we get here then we need to decide what the constructor was
// To successfully convert the type
return getNameValueConstructor(name, value);
default:
return '';
}
};

// Method to decide the constructors type and call its relevant method
var getNameValueConstructor = function(name, value) {
if (value.constructor == Date) {
return getNameValueDate(name, value);
} else if (value.constructor == Array) {
return getNameValueArray(name, value);
} else if (value.constructor == String) {
return getNameValueString(name, value);
} else if (value.constructor == Boolean) {
return getNameValueBoolean(name, value);
} else if (value.constructor == Number) {
return getNameValueNumber(name, value);
} else if (value.constructor == Object) {
return getNameValueObject(name, value);
} else {
return '';
}
};

// Methods to convert a name value pair to a JSON serialized string for each data type
var getNameValueString = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueString(value);
};
var getNameValueNumber = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueNumber(value);
};
var getNameValueBoolean = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueBoolean(value);
};
var getNameValueDate = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueDate(value);
};
var getNameValueNull = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueNull(value);
};
var getNameValueArray = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueArray(value);
};
var getNameValueObject = function(name, value) {
return '"' + removeInvalidChars(name) + '":' + getValueObject(value);
};

// Method to decide the variables type and call its relevant method
// This method is similiar to above but calls the methods with no name and just returns a value
var getValue = function(value) {
var t = typeof value;
if (value === undefined) { return getValueUndefined(value); }
if (value === null) { return getValueNull(value) }
switch (t) {
case 'string':
return getValueString(value);
case 'number':
return getValueNumber(value);
case 'boolean':
return getValueBoolean(value);
case 'object':
// If we get here then we need to decide what the constructor was
// To successfully convert the type
return getValueConstructor(value);
default:
return '';
}
};

// Method to decide the constructors type and call its relevant method
// This method is similiar to above but calls the methods with no name and just returns a value
var getValueConstructor = function(value) {
if (value.constructor == Date) {
return getValueDate(value);
} else if (value.constructor == Array) {
return getValueArray(value);
} else if (value.constructor == String) {
return getValueString(value);
} else if (value.constructor == Boolean) {
return getValueBoolean(value);
} else if (value.constructor == Number) {
return getValueNumber(value);
} else if (value.constructor == Object) {
return getValueObject(value);
} else {
return '';
}
};

// Methods to convert a value into its JSON format
var getValueString = function(value) {
return '"' + removeInvalidChars(value) + '"';
};
var getValueNumber = function(value) {
return value;
};
var getValueBoolean = function(value) {
return value ? 'true' : 'false';
};
var getValueDate = function(value) {
return '"\\/Date(' + value.getTime() + ')\\/"';
};
var getValueNull = function(value) {
return 'null';
};
var getValueUndefined = function(value) {
return '';
};
var getValueArray = function(value) {
var json = '[';
var first = true;
var copy = removeFromArray(value, undefined); // Remove undefined values
for (var i in copy) {
json += first ? '' : ',';
json += getValue(copy[i]);
first = false;
}
return json + ']';
};
var getValueObject = function(value) {
var json = '{';
var first = true;
var copy = removeFromObject(value, undefined); // Remove undefined values
for (var i in copy) {
json += first ? '' : ',';
json += '"' + removeInvalidChars(i) + '":' + getValue(copy[i]);
first = false;
}
return json + '}';
};

// Method to remove invalid chars, unescape or other methods will destroy strings formatting
var removeInvalidChars = function(value) {
return String(value).replace(/["]/g, "`");
};

// Method to remove all instances of a value from an array
var removeFromArray = function(array, value) {
var i = 0;
while (i < array.length) {
if (array[i] == item) {
array.splice(i, 1);
} else {
i++;
}
}
return array;
};

// Method to remove all instances of a value from an object
var removeFromObject = function(object, value) {
var copy = {};
for (var i in object) {
if (object[i] !== undefined) { copy[i] = object[i]; }
}
return copy;
};

return {

serialize: function(o) {
var json = '{';
var first = true;
for (var i in o) {
json += first ? '' : ',';
json += getNameValue(i, o[i]);
first = false;
}
return json + '}';
}

};

} ();

The code above seems commented enough for somebody to follow it through but if more clarification is needed just drop me a message. Ive also minified the code for use as an external library if nescessary. At just under 4kb i can't complain about the outcome and it seems to work with no problems. Hope it helps.

No comments:

Post a Comment