]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Format/JSON.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Format / JSON.js
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. */
4
5 /**
6  * Note:
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.
10  */
11
12 /**
13  * @requires OpenLayers/Format.js
14  */
15
16 /**
17  * Class: OpenLayers.Format.JSON
18  * A parser to read/write JSON safely.  Create a new instance with the
19  *     <OpenLayers.Format.JSON> constructor.
20  *
21  * Inherits from:
22  *  - <OpenLayers.Format>
23  */
24 OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
25     
26     /**
27      * APIProperty: indent
28      * {String} For "pretty" printing, the indent string will be used once for
29      *     each indentation level.
30      */
31     indent: "    ",
32     
33     /**
34      * APIProperty: space
35      * {String} For "pretty" printing, the space string will be used after
36      *     the ":" separating a name/value pair.
37      */
38     space: " ",
39     
40     /**
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.
44      */
45     newline: "\n",
46     
47     /**
48      * Property: level
49      * {Integer} For "pretty" printing, this is incremented/decremented during
50      *     serialization.
51      */
52     level: 0,
53
54     /**
55      * Property: pretty
56      * {Boolean} Serialize with extra whitespace for structure.  This is set
57      *     by the <write> method.
58      */
59     pretty: false,
60
61     /**
62      * Constructor: OpenLayers.Format.JSON
63      * Create a new parser for JSON.
64      *
65      * Parameters:
66      * options - {Object} An optional object whose properties will be set on
67      *     this instance.
68      */
69     initialize: function(options) {
70         OpenLayers.Format.prototype.initialize.apply(this, [options]);
71     },
72
73     /**
74      * APIMethod: read
75      * Deserialize a json string.
76      *
77      * Parameters:
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.
84      *     
85      * Returns:
86      * {Object} An object, array, string, or number .
87      */
88     read: function(json, filter) {
89         /**
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
95          *     characters.
96          */
97         try {
98             if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
99                                 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
100                                 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
101
102                 /**
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.
108                  */
109                 var object = eval('(' + json + ')');
110
111                 /**
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.
115                  */
116                 if(typeof filter === 'function') {
117                     function walk(k, v) {
118                         if(v && typeof v === 'object') {
119                             for(var i in v) {
120                                 if(v.hasOwnProperty(i)) {
121                                     v[i] = walk(i, v[i]);
122                                 }
123                             }
124                         }
125                         return filter(k, v);
126                     }
127                     object = walk('', object);
128                 }
129
130                 if(this.keepData) {
131                     this.data = object;
132                 }
133
134                 return object;
135             }
136         } catch(e) {
137             // Fall through if the regexp test fails.
138         }
139         return null;
140     },
141
142     /**
143      * APIMethod: write
144      * Serialize an object into a JSON string.
145      *
146      * Parameters:
147      * value - {String} The object, array, string, number, boolean or date
148      *     to be serialized.
149      * pretty - {Boolean} Structure the output with newlines and indentation.
150      *     Default is false.
151      *
152      * Returns:
153      * {String} The JSON string representation of the input value.
154      */
155     write: function(value, pretty) {
156         this.pretty = !!pretty;
157         var json = null;
158         var type = typeof value;
159         if(this.serialize[type]) {
160             try {
161                 json = this.serialize[type].apply(this, [value]);
162             } catch(err) {
163                 OpenLayers.Console.error("Trouble serializing: " + err);
164             }
165         }
166         return json;
167     },
168     
169     /**
170      * Method: writeIndent
171      * Output an indentation string depending on the indentation level.
172      *
173      * Returns:
174      * {String} An appropriate indentation string.
175      */
176     writeIndent: function() {
177         var pieces = [];
178         if(this.pretty) {
179             for(var i=0; i<this.level; ++i) {
180                 pieces.push(this.indent);
181             }
182         }
183         return pieces.join('');
184     },
185     
186     /**
187      * Method: writeNewline
188      * Output a string representing a newline if in pretty printing mode.
189      *
190      * Returns:
191      * {String} A string representing a new line.
192      */
193     writeNewline: function() {
194         return (this.pretty) ? this.newline : '';
195     },
196     
197     /**
198      * Method: writeSpace
199      * Output a string representing a space if in pretty printing mode.
200      *
201      * Returns:
202      * {String} A space.
203      */
204     writeSpace: function() {
205         return (this.pretty) ? this.space : '';
206     },
207
208     /**
209      * Property: serialize
210      * Object with properties corresponding to the serializable data types.
211      *     Property values are functions that do the actual serializing.
212      */
213     serialize: {
214         /**
215          * Method: serialize.object
216          * Transform an object into a JSON string.
217          *
218          * Parameters:
219          * object - {Object} The object to be serialized.
220          * 
221          * Returns:
222          * {String} A JSON string representing the object.
223          */
224         'object': function(object) {
225             // three special objects that we want to treat differently
226             if(object == null) {
227                 return "null";
228             }
229             if(object.constructor == Date) {
230                 return this.serialize.date.apply(this, [object]);
231             }
232             if(object.constructor == Array) {
233                 return this.serialize.array.apply(this, [object]);
234             }
235             var pieces = ['{'];
236             this.level += 1;
237             var key, keyJSON, valueJSON;
238             
239             var addComma = false;
240             for(key in object) {
241                 if(object.hasOwnProperty(key)) {
242                     // recursive calls need to allow for sub-classing
243                     keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
244                                                     [key, this.pretty]);
245                     valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
246                                                     [object[key], this.pretty]);
247                     if(keyJSON != null && valueJSON != null) {
248                         if(addComma) {
249                             pieces.push(',');
250                         }
251                         pieces.push(this.writeNewline(), this.writeIndent(),
252                                     keyJSON, ':', this.writeSpace(), valueJSON);
253                         addComma = true;
254                     }
255                 }
256             }
257             
258             this.level -= 1;
259             pieces.push(this.writeNewline(), this.writeIndent(), '}');
260             return pieces.join('');
261         },
262         
263         /**
264          * Method: serialize.array
265          * Transform an array into a JSON string.
266          *
267          * Parameters:
268          * array - {Array} The array to be serialized
269          * 
270          * Returns:
271          * {String} A JSON string representing the array.
272          */
273         'array': function(array) {
274             var json;
275             var pieces = ['['];
276             this.level += 1;
277     
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]);
282                 if(json != null) {
283                     if(i > 0) {
284                         pieces.push(',');
285                     }
286                     pieces.push(this.writeNewline(), this.writeIndent(), json);
287                 }
288             }
289
290             this.level -= 1;    
291             pieces.push(this.writeNewline(), this.writeIndent(), ']');
292             return pieces.join('');
293         },
294         
295         /**
296          * Method: serialize.string
297          * Transform a string into a JSON string.
298          *
299          * Parameters:
300          * string - {String} The string to be serialized
301          * 
302          * Returns:
303          * {String} A JSON string representing the string.
304          */
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
309             // sequences.    
310             var m = {
311                 '\b': '\\b',
312                 '\t': '\\t',
313                 '\n': '\\n',
314                 '\f': '\\f',
315                 '\r': '\\r',
316                 '"' : '\\"',
317                 '\\': '\\\\'
318             };
319             if(/["\\\x00-\x1f]/.test(string)) {
320                 return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
321                     var c = m[b];
322                     if(c) {
323                         return c;
324                     }
325                     c = b.charCodeAt();
326                     return '\\u00' +
327                         Math.floor(c / 16).toString(16) +
328                         (c % 16).toString(16);
329                 }) + '"';
330             }
331             return '"' + string + '"';
332         },
333
334         /**
335          * Method: serialize.number
336          * Transform a number into a JSON string.
337          *
338          * Parameters:
339          * number - {Number} The number to be serialized.
340          *
341          * Returns:
342          * {String} A JSON string representing the number.
343          */
344         'number': function(number) {
345             return isFinite(number) ? String(number) : "null";
346         },
347         
348         /**
349          * Method: serialize.boolean
350          * Transform a boolean into a JSON string.
351          *
352          * Parameters:
353          * bool - {Boolean} The boolean to be serialized.
354          * 
355          * Returns:
356          * {String} A JSON string representing the boolean.
357          */
358         'boolean': function(bool) {
359             return String(bool);
360         },
361         
362         /**
363          * Method: serialize.object
364          * Transform a date into a JSON string.
365          *
366          * Parameters:
367          * date - {Date} The date to be serialized.
368          * 
369          * Returns:
370          * {String} A JSON string representing the date.
371          */
372         'date': function(date) {    
373             function format(number) {
374                 // Format integers to have at least two digits.
375                 return (number < 10) ? '0' + number : number;
376             }
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()) + '"';
383         }
384     },
385
386     CLASS_NAME: "OpenLayers.Format.JSON" 
387
388 });