1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
7 * This work draws heavily from the public domain JSON serializer/deserializer
8 * at http://www.json.org/json.js. Rewritten so that it doesn't modify
9 * basic data prototypes.
13 * @requires OpenLayers/Format.js
17 * Class: OpenLayers.Format.JSON
18 * A parser to read/write JSON safely. Create a new instance with the
19 * <OpenLayers.Format.JSON> constructor.
22 * - <OpenLayers.Format>
24 OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
28 * {String} For "pretty" printing, the indent string will be used once for
29 * each indentation level.
35 * {String} For "pretty" printing, the space string will be used after
36 * the ":" separating a name/value pair.
41 * APIProperty: newline
42 * {String} For "pretty" printing, the newline string will be used at the
43 * end of each name/value pair or array item.
49 * {Integer} For "pretty" printing, this is incremented/decremented during
56 * {Boolean} Serialize with extra whitespace for structure. This is set
57 * by the <write> method.
62 * Constructor: OpenLayers.Format.JSON
63 * Create a new parser for JSON.
66 * options - {Object} An optional object whose properties will be set on
69 initialize: function(options) {
70 OpenLayers.Format.prototype.initialize.apply(this, [options]);
75 * Deserialize a json string.
78 * json - {String} A JSON string
79 * filter - {Function} A function which will be called for every key and
80 * value at every level of the final result. Each value will be
81 * replaced by the result of the filter function. This can be used to
82 * reform generic objects into instances of classes, or to transform
83 * date strings into Date objects.
86 * {Object} An object, array, string, or number .
88 read: function(json, filter) {
90 * Parsing happens in three stages. In the first stage, we run the text
91 * against a regular expression which looks for non-JSON
92 * characters. We are especially concerned with '()' and 'new'
93 * because they can cause invocation, and '=' because it can cause
94 * mutation. But just to be safe, we will reject all unexpected
98 if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
99 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
100 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
103 * In the second stage we use the eval function to compile the
104 * text into a JavaScript structure. The '{' operator is
105 * subject to a syntactic ambiguity in JavaScript - it can
106 * begin a block or an object literal. We wrap the text in
107 * parens to eliminate the ambiguity.
109 var object = eval('(' + json + ')');
112 * In the optional third stage, we recursively walk the new
113 * structure, passing each name/value pair to a filter
114 * function for possible transformation.
116 if(typeof filter === 'function') {
117 function walk(k, v) {
118 if(v && typeof v === 'object') {
120 if(v.hasOwnProperty(i)) {
121 v[i] = walk(i, v[i]);
127 object = walk('', object);
137 // Fall through if the regexp test fails.
144 * Serialize an object into a JSON string.
147 * value - {String} The object, array, string, number, boolean or date
149 * pretty - {Boolean} Structure the output with newlines and indentation.
153 * {String} The JSON string representation of the input value.
155 write: function(value, pretty) {
156 this.pretty = !!pretty;
158 var type = typeof value;
159 if(this.serialize[type]) {
161 json = this.serialize[type].apply(this, [value]);
163 OpenLayers.Console.error("Trouble serializing: " + err);
170 * Method: writeIndent
171 * Output an indentation string depending on the indentation level.
174 * {String} An appropriate indentation string.
176 writeIndent: function() {
179 for(var i=0; i<this.level; ++i) {
180 pieces.push(this.indent);
183 return pieces.join('');
187 * Method: writeNewline
188 * Output a string representing a newline if in pretty printing mode.
191 * {String} A string representing a new line.
193 writeNewline: function() {
194 return (this.pretty) ? this.newline : '';
199 * Output a string representing a space if in pretty printing mode.
204 writeSpace: function() {
205 return (this.pretty) ? this.space : '';
209 * Property: serialize
210 * Object with properties corresponding to the serializable data types.
211 * Property values are functions that do the actual serializing.
215 * Method: serialize.object
216 * Transform an object into a JSON string.
219 * object - {Object} The object to be serialized.
222 * {String} A JSON string representing the object.
224 'object': function(object) {
225 // three special objects that we want to treat differently
229 if(object.constructor == Date) {
230 return this.serialize.date.apply(this, [object]);
232 if(object.constructor == Array) {
233 return this.serialize.array.apply(this, [object]);
237 var key, keyJSON, valueJSON;
239 var addComma = false;
241 if(object.hasOwnProperty(key)) {
242 // recursive calls need to allow for sub-classing
243 keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
245 valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
246 [object[key], this.pretty]);
247 if(keyJSON != null && valueJSON != null) {
251 pieces.push(this.writeNewline(), this.writeIndent(),
252 keyJSON, ':', this.writeSpace(), valueJSON);
259 pieces.push(this.writeNewline(), this.writeIndent(), '}');
260 return pieces.join('');
264 * Method: serialize.array
265 * Transform an array into a JSON string.
268 * array - {Array} The array to be serialized
271 * {String} A JSON string representing the array.
273 'array': function(array) {
278 for(var i=0, len=array.length; i<len; ++i) {
279 // recursive calls need to allow for sub-classing
280 json = OpenLayers.Format.JSON.prototype.write.apply(this,
281 [array[i], this.pretty]);
286 pieces.push(this.writeNewline(), this.writeIndent(), json);
291 pieces.push(this.writeNewline(), this.writeIndent(), ']');
292 return pieces.join('');
296 * Method: serialize.string
297 * Transform a string into a JSON string.
300 * string - {String} The string to be serialized
303 * {String} A JSON string representing the string.
305 'string': function(string) {
306 // If the string contains no control characters, no quote characters, and no
307 // backslash characters, then we can simply slap some quotes around it.
308 // Otherwise we must also replace the offending characters with safe
319 if(/["\\\x00-\x1f]/.test(string)) {
320 return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
327 Math.floor(c / 16).toString(16) +
328 (c % 16).toString(16);
331 return '"' + string + '"';
335 * Method: serialize.number
336 * Transform a number into a JSON string.
339 * number - {Number} The number to be serialized.
342 * {String} A JSON string representing the number.
344 'number': function(number) {
345 return isFinite(number) ? String(number) : "null";
349 * Method: serialize.boolean
350 * Transform a boolean into a JSON string.
353 * bool - {Boolean} The boolean to be serialized.
356 * {String} A JSON string representing the boolean.
358 'boolean': function(bool) {
363 * Method: serialize.object
364 * Transform a date into a JSON string.
367 * date - {Date} The date to be serialized.
370 * {String} A JSON string representing the date.
372 'date': function(date) {
373 function format(number) {
374 // Format integers to have at least two digits.
375 return (number < 10) ? '0' + number : number;
377 return '"' + date.getFullYear() + '-' +
378 format(date.getMonth() + 1) + '-' +
379 format(date.getDate()) + 'T' +
380 format(date.getHours()) + ':' +
381 format(date.getMinutes()) + ':' +
382 format(date.getSeconds()) + '"';
386 CLASS_NAME: "OpenLayers.Format.JSON"