Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / data / reader / Json.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Ed Spencer
17  * @class Ext.data.reader.Json
18  * @extends Ext.data.reader.Reader
19  *
20  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
21  * happens as a result of loading a Store - for example we might create something like this:</p>
22  *
23 <pre><code>
24 Ext.define('User', {
25     extend: 'Ext.data.Model',
26     fields: ['id', 'name', 'email']
27 });
28
29 var store = Ext.create('Ext.data.Store', {
30     model: 'User',
31     proxy: {
32         type: 'ajax',
33         url : 'users.json',
34         reader: {
35             type: 'json'
36         }
37     }
38 });
39 </code></pre>
40  *
41  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
42  * not already familiar with them.</p>
43  *
44  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
45  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
46  * Store, so it is as if we passed this instead:
47  *
48 <pre><code>
49 reader: {
50     type : 'json',
51     model: 'User'
52 }
53 </code></pre>
54  *
55  * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
56  *
57 <pre><code>
58 [
59     {
60         "id": 1,
61         "name": "Ed Spencer",
62         "email": "ed@sencha.com"
63     },
64     {
65         "id": 2,
66         "name": "Abe Elias",
67         "email": "abe@sencha.com"
68     }
69 ]
70 </code></pre>
71  *
72  * <p><u>Reading other JSON formats</u></p>
73  *
74  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
75  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
76  * {@link #root} configuration to parse data that comes back like this:</p>
77  *
78 <pre><code>
79 {
80     "users": [
81        {
82            "id": 1,
83            "name": "Ed Spencer",
84            "email": "ed@sencha.com"
85        },
86        {
87            "id": 2,
88            "name": "Abe Elias",
89            "email": "abe@sencha.com"
90        }
91     ]
92 }
93 </code></pre>
94  *
95  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
96  *
97 <pre><code>
98 reader: {
99     type: 'json',
100     root: 'users'
101 }
102 </code></pre>
103  *
104  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
105  * around each record inside a nested structure like this:</p>
106  *
107 <pre><code>
108 {
109     "total": 122,
110     "offset": 0,
111     "users": [
112         {
113             "id": "ed-spencer-1",
114             "value": 1,
115             "user": {
116                 "id": 1,
117                 "name": "Ed Spencer",
118                 "email": "ed@sencha.com"
119             }
120         }
121     ]
122 }
123 </code></pre>
124  *
125  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
126  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
127  * JSON above we need to specify the {@link #record} configuration like this:</p>
128  *
129 <pre><code>
130 reader: {
131     type  : 'json',
132     root  : 'users',
133     record: 'user'
134 }
135 </code></pre>
136  *
137  * <p><u>Response metadata</u></p>
138  *
139  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
140  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
141  * like this:</p>
142  *
143 <pre><code>
144 {
145     "total": 100,
146     "success": true,
147     "users": [
148         {
149             "id": 1,
150             "name": "Ed Spencer",
151             "email": "ed@sencha.com"
152         }
153     ]
154 }
155 </code></pre>
156  *
157  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
158  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
159  * options:</p>
160  *
161 <pre><code>
162 reader: {
163     type : 'json',
164     root : 'users',
165     totalProperty  : 'total',
166     successProperty: 'success'
167 }
168 </code></pre>
169  *
170  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
171  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
172  * returned.</p>
173  */
174 Ext.define('Ext.data.reader.Json', {
175     extend: 'Ext.data.reader.Reader',
176     alternateClassName: 'Ext.data.JsonReader',
177     alias : 'reader.json',
178
179     root: '',
180
181     /**
182      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
183      * See the JsonReader intro docs for more details. This is not often needed.
184      */
185
186     /**
187      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
188      * reading values. Defalts to <tt>false</tt>.
189      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
190      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
191      * "foo.bar.baz" direct from the root object.
192      */
193     useSimpleAccessors: false,
194
195     /**
196      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
197      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
198      * @param {Object} data The raw JSON data
199      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
200      */
201     readRecords: function(data) {
202         //this has to be before the call to super because we use the meta data in the superclass readRecords
203         if (data.metaData) {
204             this.onMetaChange(data.metaData);
205         }
206
207         /**
208          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
209          * @property {Object} jsonData
210          */
211         this.jsonData = data;
212         return this.callParent([data]);
213     },
214
215     //inherit docs
216     getResponseData: function(response) {
217         var data;
218         try {
219             data = Ext.decode(response.responseText);
220         }
221         catch (ex) {
222             Ext.Error.raise({
223                 response: response,
224                 json: response.responseText,
225                 parseError: ex,
226                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
227             });
228         }
229         //<debug>
230         if (!data) {
231             Ext.Error.raise('JSON object not found');
232         }
233         //</debug>
234
235         return data;
236     },
237
238     //inherit docs
239     buildExtractors : function() {
240         var me = this;
241
242         me.callParent(arguments);
243
244         if (me.root) {
245             me.getRoot = me.createAccessor(me.root);
246         } else {
247             me.getRoot = function(root) {
248                 return root;
249             };
250         }
251     },
252
253     /**
254      * @private
255      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
256      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
257      * @param {Object} root The JSON root node
258      * @return {Ext.data.Model[]} The records
259      */
260     extractData: function(root) {
261         var recordName = this.record,
262             data = [],
263             length, i;
264
265         if (recordName) {
266             length = root.length;
267             
268             if (!length && Ext.isObject(root)) {
269                 length = 1;
270                 root = [root];
271             }
272
273             for (i = 0; i < length; i++) {
274                 data[i] = root[i][recordName];
275             }
276         } else {
277             data = root;
278         }
279         return this.callParent([data]);
280     },
281
282     /**
283      * @private
284      * Returns an accessor function for the given property string. Gives support for properties such as the following:
285      * 'someProperty'
286      * 'some.property'
287      * 'some["property"]'
288      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
289      */
290     createAccessor: function() {
291         var re = /[\[\.]/;
292
293         return function(expr) {
294             if (Ext.isEmpty(expr)) {
295                 return Ext.emptyFn;
296             }
297             if (Ext.isFunction(expr)) {
298                 return expr;
299             }
300             if (this.useSimpleAccessors !== true) {
301                 var i = String(expr).search(re);
302                 if (i >= 0) {
303                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
304                 }
305             }
306             return function(obj) {
307                 return obj[expr];
308             };
309         };
310     }()
311 });