|
1 /* |
|
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
3 Code licensed under the BSD License: |
|
4 http://developer.yahoo.net/yui/license.txt |
|
5 version: 3.0.0 |
|
6 build: 1549 |
|
7 */ |
|
8 YUI.add('dataschema-json', function(Y) { |
|
9 |
|
10 /** |
|
11 * Provides a DataSchema implementation which can be used to work with JSON data. |
|
12 * |
|
13 * @module dataschema |
|
14 * @submodule dataschema-json |
|
15 */ |
|
16 |
|
17 /** |
|
18 * JSON subclass for the DataSchema Utility. |
|
19 * @class DataSchema.JSON |
|
20 * @extends DataSchema.Base |
|
21 * @static |
|
22 */ |
|
23 var LANG = Y.Lang, |
|
24 |
|
25 SchemaJSON = { |
|
26 |
|
27 ///////////////////////////////////////////////////////////////////////////// |
|
28 // |
|
29 // DataSchema.JSON static methods |
|
30 // |
|
31 ///////////////////////////////////////////////////////////////////////////// |
|
32 /** |
|
33 * Utility function converts JSON locator strings into walkable paths |
|
34 * |
|
35 * @method DataSchema.JSON.getPath |
|
36 * @param locator {String} JSON value locator. |
|
37 * @return {String[]} Walkable path to data value. |
|
38 * @static |
|
39 */ |
|
40 getPath: function(locator) { |
|
41 var path = null, |
|
42 keys = [], |
|
43 i = 0; |
|
44 |
|
45 if (locator) { |
|
46 // Strip the ["string keys"] and [1] array indexes |
|
47 locator = locator. |
|
48 replace(/\[(['"])(.*?)\1\]/g, |
|
49 function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}). |
|
50 replace(/\[(\d+)\]/g, |
|
51 function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}). |
|
52 replace(/^\./,''); // remove leading dot |
|
53 |
|
54 // Validate against problematic characters. |
|
55 if (!/[^\w\.\$@]/.test(locator)) { |
|
56 path = locator.split('.'); |
|
57 for (i=path.length-1; i >= 0; --i) { |
|
58 if (path[i].charAt(0) === '@') { |
|
59 path[i] = keys[parseInt(path[i].substr(1),10)]; |
|
60 } |
|
61 } |
|
62 } |
|
63 else { |
|
64 Y.log("Invalid locator: " + locator, "error", "dataschema-json"); |
|
65 } |
|
66 } |
|
67 return path; |
|
68 }, |
|
69 |
|
70 /** |
|
71 * Utility function to walk a path and return the value located there. |
|
72 * |
|
73 * @method DataSchema.JSON.getLocationValue |
|
74 * @param path {String[]} Locator path. |
|
75 * @param data {String} Data to traverse. |
|
76 * @return {Object} Data value at location. |
|
77 * @static |
|
78 */ |
|
79 getLocationValue: function (path, data) { |
|
80 var i = 0, |
|
81 len = path.length; |
|
82 for (;i<len;i++) { |
|
83 if(!LANG.isUndefined(data[path[i]])) { |
|
84 data = data[path[i]]; |
|
85 } |
|
86 else { |
|
87 data = undefined; |
|
88 break; |
|
89 } |
|
90 } |
|
91 return data; |
|
92 }, |
|
93 |
|
94 /** |
|
95 * Applies a given schema to given JSON data. |
|
96 * |
|
97 * @method apply |
|
98 * @param schema {Object} Schema to apply. |
|
99 * @param data {Object} JSON data. |
|
100 * @return {Object} Schema-parsed data. |
|
101 * @static |
|
102 */ |
|
103 apply: function(schema, data) { |
|
104 var data_in = data, |
|
105 data_out = {results:[],meta:{}}; |
|
106 |
|
107 // Convert incoming JSON strings |
|
108 if(!LANG.isObject(data)) { |
|
109 try { |
|
110 data_in = Y.JSON.parse(data); |
|
111 } |
|
112 catch(e) { |
|
113 data_out.error = e; |
|
114 return data_out; |
|
115 } |
|
116 } |
|
117 |
|
118 if(LANG.isObject(data_in) && schema) { |
|
119 // Parse results data |
|
120 if(!LANG.isUndefined(schema.resultListLocator)) { |
|
121 data_out = SchemaJSON._parseResults(schema, data_in, data_out); |
|
122 } |
|
123 |
|
124 // Parse meta data |
|
125 if(!LANG.isUndefined(schema.metaFields)) { |
|
126 data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out); |
|
127 } |
|
128 } |
|
129 else { |
|
130 Y.log("JSON data could not be schema-parsed: " + Y.dump(data) + " " + Y.dump(data), "error", "dataschema-json"); |
|
131 data_out.error = new Error("JSON schema parse failure"); |
|
132 } |
|
133 |
|
134 return data_out; |
|
135 }, |
|
136 |
|
137 /** |
|
138 * Schema-parsed list of results from full data |
|
139 * |
|
140 * @method _parseResults |
|
141 * @param schema {Object} Schema to parse against. |
|
142 * @param json_in {Object} JSON to parse. |
|
143 * @param data_out {Object} In-progress parsed data to update. |
|
144 * @return {Object} Parsed data object. |
|
145 * @static |
|
146 * @protected |
|
147 */ |
|
148 _parseResults: function(schema, json_in, data_out) { |
|
149 var results = [], |
|
150 path, |
|
151 error; |
|
152 |
|
153 if(schema.resultListLocator) { |
|
154 path = SchemaJSON.getPath(schema.resultListLocator); |
|
155 if(path) { |
|
156 results = SchemaJSON.getLocationValue(path, json_in); |
|
157 if (results === undefined) { |
|
158 data_out.results = []; |
|
159 error = new Error("JSON results retrieval failure"); |
|
160 } |
|
161 else { |
|
162 if(LANG.isArray(schema.resultFields) && LANG.isArray(results)) { |
|
163 data_out = SchemaJSON._getFieldValues(schema.resultFields, results, data_out); |
|
164 } |
|
165 else { |
|
166 data_out.results = []; |
|
167 error = new Error("JSON Schema fields retrieval failure"); |
|
168 } |
|
169 } |
|
170 } |
|
171 else { |
|
172 error = new Error("JSON Schema results locator failure"); |
|
173 } |
|
174 |
|
175 if (error) { |
|
176 Y.log("JSON data could not be parsed: " + Y.dump(json_in), "error", "dataschema-json"); |
|
177 data_out.error = error; |
|
178 } |
|
179 |
|
180 } |
|
181 return data_out; |
|
182 }, |
|
183 |
|
184 /** |
|
185 * Get field data values out of list of full results |
|
186 * |
|
187 * @method _getFieldValues |
|
188 * @param fields {Array} Fields to find. |
|
189 * @param array_in {Array} Results to parse. |
|
190 * @param data_out {Object} In-progress parsed data to update. |
|
191 * @return {Object} Parsed data object. |
|
192 * @static |
|
193 * @protected |
|
194 */ |
|
195 _getFieldValues: function(fields, array_in, data_out) { |
|
196 var results = [], |
|
197 len = fields.length, |
|
198 i, j, |
|
199 field, key, path, parser, |
|
200 simplePaths = [], complexPaths = [], fieldParsers = [], |
|
201 result, record; |
|
202 |
|
203 // First collect hashes of simple paths, complex paths, and parsers |
|
204 for (i=0; i<len; i++) { |
|
205 field = fields[i]; // A field can be a simple string or a hash |
|
206 key = field.key || field; // Find the key |
|
207 |
|
208 // Validate and store locators for later |
|
209 path = SchemaJSON.getPath(key); |
|
210 if (path) { |
|
211 if (path.length === 1) { |
|
212 simplePaths[simplePaths.length] = {key:key, path:path[0]}; |
|
213 } else { |
|
214 complexPaths[complexPaths.length] = {key:key, path:path}; |
|
215 } |
|
216 } else { |
|
217 Y.log("Invalid key syntax: " + key, "warn", "dataschema-json"); |
|
218 } |
|
219 |
|
220 // Validate and store parsers for later |
|
221 //TODO: use Y.DataSchema.parse? |
|
222 parser = (LANG.isFunction(field.parser)) ? field.parser : Y.Parsers[field.parser+'']; |
|
223 if (parser) { |
|
224 fieldParsers[fieldParsers.length] = {key:key, parser:parser}; |
|
225 } |
|
226 } |
|
227 |
|
228 // Traverse list of array_in, creating records of simple fields, |
|
229 // complex fields, and applying parsers as necessary |
|
230 for (i=array_in.length-1; i>=0; --i) { |
|
231 record = {}; |
|
232 result = array_in[i]; |
|
233 if(result) { |
|
234 // Cycle through simpleLocators |
|
235 for (j=simplePaths.length-1; j>=0; --j) { |
|
236 // Bug 1777850: The result might be an array instead of object |
|
237 record[simplePaths[j].key] = Y.DataSchema.Base.parse( |
|
238 (LANG.isUndefined(result[simplePaths[j].path]) ? |
|
239 result[j] : result[simplePaths[j].path]), simplePaths[j]); |
|
240 } |
|
241 |
|
242 // Cycle through complexLocators |
|
243 for (j=complexPaths.length - 1; j>=0; --j) { |
|
244 record[complexPaths[j].key] = Y.DataSchema.Base.parse( |
|
245 (SchemaJSON.getLocationValue(complexPaths[j].path, result)), complexPaths[j] ); |
|
246 } |
|
247 |
|
248 // Cycle through fieldParsers |
|
249 for (j=fieldParsers.length-1; j>=0; --j) { |
|
250 key = fieldParsers[j].key; |
|
251 record[key] = fieldParsers[j].parser(record[key]); |
|
252 // Safety net |
|
253 if (LANG.isUndefined(record[key])) { |
|
254 record[key] = null; |
|
255 } |
|
256 } |
|
257 results[i] = record; |
|
258 } |
|
259 } |
|
260 data_out.results = results; |
|
261 return data_out; |
|
262 }, |
|
263 |
|
264 /** |
|
265 * Parses results data according to schema |
|
266 * |
|
267 * @method _parseMeta |
|
268 * @param metaFields {Object} Metafields definitions. |
|
269 * @param json_in {Object} JSON to parse. |
|
270 * @param data_out {Object} In-progress parsed data to update. |
|
271 * @return {Object} Schema-parsed meta data. |
|
272 * @static |
|
273 * @protected |
|
274 */ |
|
275 _parseMeta: function(metaFields, json_in, data_out) { |
|
276 if(LANG.isObject(metaFields)) { |
|
277 var key, path; |
|
278 for(key in metaFields) { |
|
279 if (metaFields.hasOwnProperty(key)) { |
|
280 path = SchemaJSON.getPath(metaFields[key]); |
|
281 if (path && json_in) { |
|
282 data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in); |
|
283 } |
|
284 } |
|
285 } |
|
286 } |
|
287 else { |
|
288 data_out.error = new Error("JSON meta data retrieval failure"); |
|
289 } |
|
290 return data_out; |
|
291 } |
|
292 }; |
|
293 |
|
294 Y.DataSchema.JSON = Y.mix(SchemaJSON, Y.DataSchema.Base); |
|
295 |
|
296 |
|
297 |
|
298 }, '3.0.0' ,{requires:['json', 'dataschema-base']}); |