[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('dataschema-json', function (Y, NAME) { 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 Provides a DataSchema implementation which can be used to work with JSON data. 19 20 See the `apply` method for usage. 21 22 @class DataSchema.JSON 23 @extends DataSchema.Base 24 @static 25 **/ 26 var LANG = Y.Lang, 27 isFunction = LANG.isFunction, 28 isObject = LANG.isObject, 29 isArray = LANG.isArray, 30 // TODO: I don't think the calls to Base.* need to be done via Base since 31 // Base is mixed into SchemaJSON. Investigate for later. 32 Base = Y.DataSchema.Base, 33 34 SchemaJSON; 35 36 SchemaJSON = { 37 38 ///////////////////////////////////////////////////////////////////////////// 39 // 40 // DataSchema.JSON static methods 41 // 42 ///////////////////////////////////////////////////////////////////////////// 43 /** 44 * Utility function converts JSON locator strings into walkable paths 45 * 46 * @method getPath 47 * @param locator {String} JSON value locator. 48 * @return {String[]} Walkable path to data value. 49 * @static 50 */ 51 getPath: function(locator) { 52 var path = null, 53 keys = [], 54 i = 0; 55 56 if (locator) { 57 // Strip the ["string keys"] and [1] array indexes 58 // TODO: the first two steps can probably be reduced to one with 59 // /\[\s*(['"])?(.*?)\1\s*\]/g, but the array indices would be 60 // stored as strings. This is not likely an issue. 61 locator = locator. 62 replace(/\[\s*(['"])(.*?)\1\s*\]/g, 63 function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}). 64 replace(/\[(\d+)\]/g, 65 function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}). 66 replace(/^\./,''); // remove leading dot 67 68 // Validate against problematic characters. 69 // commented out because the path isn't sent to eval, so it 70 // should be safe. I'm not sure what makes a locator invalid. 71 //if (!/[^\w\.\$@]/.test(locator)) { 72 path = locator.split('.'); 73 for (i=path.length-1; i >= 0; --i) { 74 if (path[i].charAt(0) === '@') { 75 path[i] = keys[parseInt(path[i].substr(1),10)]; 76 } 77 } 78 /*} 79 else { 80 Y.log("Invalid locator: " + locator, "error", "dataschema-json"); 81 } 82 */ 83 } 84 return path; 85 }, 86 87 /** 88 * Utility function to walk a path and return the value located there. 89 * 90 * @method getLocationValue 91 * @param path {String[]} Locator path. 92 * @param data {String} Data to traverse. 93 * @return {Object} Data value at location. 94 * @static 95 */ 96 getLocationValue: function (path, data) { 97 var i = 0, 98 len = path.length; 99 for (;i<len;i++) { 100 if (isObject(data) && (path[i] in data)) { 101 data = data[path[i]]; 102 } else { 103 data = undefined; 104 break; 105 } 106 } 107 return data; 108 }, 109 110 /** 111 Applies a schema to an array of data located in a JSON structure, returning 112 a normalized object with results in the `results` property. Additional 113 information can be parsed out of the JSON for inclusion in the `meta` 114 property of the response object. If an error is encountered during 115 processing, an `error` property will be added. 116 117 The input _data_ is expected to be an object or array. If it is a string, 118 it will be passed through `Y.JSON.parse()`. 119 120 If _data_ contains an array of data records to normalize, specify the 121 _schema.resultListLocator_ as a dot separated path string just as you would 122 reference it in JavaScript. So if your _data_ object has a record array at 123 _data.response.results_, use _schema.resultListLocator_ = 124 "response.results". Bracket notation can also be used for array indices or 125 object properties (e.g. "response['results']"); This is called a "path 126 locator" 127 128 Field data in the result list is extracted with field identifiers in 129 _schema.resultFields_. Field identifiers are objects with the following 130 properties: 131 132 * `key` : <strong>(required)</strong> The path locator (String) 133 * `parser`: A function or the name of a function on `Y.Parsers` used 134 to convert the input value into a normalized type. Parser 135 functions are passed the value as input and are expected to 136 return a value. 137 138 If no value parsing is needed, you can use path locators (strings) 139 instead of field identifiers (objects) -- see example below. 140 141 If no processing of the result list array is needed, _schema.resultFields_ 142 can be omitted; the `response.results` will point directly to the array. 143 144 If the result list contains arrays, `response.results` will contain an 145 array of objects with key:value pairs assuming the fields in 146 _schema.resultFields_ are ordered in accordance with the data array 147 values. 148 149 If the result list contains objects, the identified _schema.resultFields_ 150 will be used to extract a value from those objects for the output result. 151 152 To extract additional information from the JSON, include an array of 153 path locators in _schema.metaFields_. The collected values will be 154 stored in `response.meta`. 155 156 157 @example 158 // Process array of arrays 159 var schema = { 160 resultListLocator: 'produce.fruit', 161 resultFields: [ 'name', 'color' ] 162 }, 163 data = { 164 produce: { 165 fruit: [ 166 [ 'Banana', 'yellow' ], 167 [ 'Orange', 'orange' ], 168 [ 'Eggplant', 'purple' ] 169 ] 170 } 171 }; 172 173 var response = Y.DataSchema.JSON.apply(schema, data); 174 175 // response.results[0] is { name: "Banana", color: "yellow" } 176 177 178 // Process array of objects + some metadata 179 schema.metaFields = [ 'lastInventory' ]; 180 181 data = { 182 produce: { 183 fruit: [ 184 { name: 'Banana', color: 'yellow', price: '1.96' }, 185 { name: 'Orange', color: 'orange', price: '2.04' }, 186 { name: 'Eggplant', color: 'purple', price: '4.31' } 187 ] 188 }, 189 lastInventory: '2011-07-19' 190 }; 191 192 response = Y.DataSchema.JSON.apply(schema, data); 193 194 // response.results[0] is { name: "Banana", color: "yellow" } 195 // response.meta.lastInventory is '2001-07-19' 196 197 198 // Use parsers 199 schema.resultFields = [ 200 { 201 key: 'name', 202 parser: function (val) { return val.toUpperCase(); } 203 }, 204 { 205 key: 'price', 206 parser: 'number' // Uses Y.Parsers.number 207 } 208 ]; 209 210 response = Y.DataSchema.JSON.apply(schema, data); 211 212 // Note price was converted from a numeric string to a number 213 // response.results[0] looks like { fruit: "BANANA", price: 1.96 } 214 215 @method apply 216 @param {Object} [schema] Schema to apply. Supported configuration 217 properties are: 218 @param {String} [schema.resultListLocator] Path locator for the 219 location of the array of records to flatten into `response.results` 220 @param {Array} [schema.resultFields] Field identifiers to 221 locate/assign values in the response records. See above for 222 details. 223 @param {Array} [schema.metaFields] Path locators to extract extra 224 non-record related information from the data object. 225 @param {Object|Array|String} data JSON data or its string serialization. 226 @return {Object} An Object with properties `results` and `meta` 227 @static 228 **/ 229 apply: function(schema, data) { 230 var data_in = data, 231 data_out = { results: [], meta: {} }; 232 233 // Convert incoming JSON strings 234 if (!isObject(data)) { 235 try { 236 data_in = Y.JSON.parse(data); 237 } 238 catch(e) { 239 data_out.error = e; 240 return data_out; 241 } 242 } 243 244 if (isObject(data_in) && schema) { 245 // Parse results data 246 data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out); 247 248 // Parse meta data 249 if (schema.metaFields !== undefined) { 250 data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out); 251 } 252 } 253 else { 254 Y.log("JSON data could not be schema-parsed: " + Y.dump(data) + " " + Y.dump(data), "error", "dataschema-json"); 255 data_out.error = new Error("JSON schema parse failure"); 256 } 257 258 return data_out; 259 }, 260 261 /** 262 * Schema-parsed list of results from full data 263 * 264 * @method _parseResults 265 * @param schema {Object} Schema to parse against. 266 * @param json_in {Object} JSON to parse. 267 * @param data_out {Object} In-progress parsed data to update. 268 * @return {Object} Parsed data object. 269 * @static 270 * @protected 271 */ 272 _parseResults: function(schema, json_in, data_out) { 273 var getPath = SchemaJSON.getPath, 274 getValue = SchemaJSON.getLocationValue, 275 path = getPath(schema.resultListLocator), 276 results = path ? 277 (getValue(path, json_in) || 278 // Fall back to treat resultListLocator as a simple key 279 json_in[schema.resultListLocator]) : 280 // Or if no resultListLocator is supplied, use the input 281 json_in; 282 283 if (isArray(results)) { 284 // if no result fields are passed in, then just take 285 // the results array whole-hog Sometimes you're getting 286 // an array of strings, or want the whole object, so 287 // resultFields don't make sense. 288 if (isArray(schema.resultFields)) { 289 data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out); 290 } else { 291 data_out.results = results; 292 } 293 } else if (schema.resultListLocator) { 294 data_out.results = []; 295 data_out.error = new Error("JSON results retrieval failure"); 296 Y.log("JSON data could not be parsed: " + Y.dump(json_in), "error", "dataschema-json"); 297 } 298 299 return data_out; 300 }, 301 302 /** 303 * Get field data values out of list of full results 304 * 305 * @method _getFieldValues 306 * @param fields {Array} Fields to find. 307 * @param array_in {Array} Results to parse. 308 * @param data_out {Object} In-progress parsed data to update. 309 * @return {Object} Parsed data object. 310 * @static 311 * @protected 312 */ 313 _getFieldValues: function(fields, array_in, data_out) { 314 var results = [], 315 len = fields.length, 316 i, j, 317 field, key, locator, path, parser, val, 318 simplePaths = [], complexPaths = [], fieldParsers = [], 319 result, record; 320 321 // First collect hashes of simple paths, complex paths, and parsers 322 for (i=0; i<len; i++) { 323 field = fields[i]; // A field can be a simple string or a hash 324 key = field.key || field; // Find the key 325 locator = field.locator || key; // Find the locator 326 327 // Validate and store locators for later 328 path = SchemaJSON.getPath(locator); 329 if (path) { 330 if (path.length === 1) { 331 simplePaths.push({ 332 key : key, 333 path: path[0] 334 }); 335 } else { 336 complexPaths.push({ 337 key : key, 338 path : path, 339 locator: locator 340 }); 341 } 342 } else { 343 Y.log("Invalid key syntax: " + key, "warn", "dataschema-json"); 344 } 345 346 // Validate and store parsers for later 347 //TODO: use Y.DataSchema.parse? 348 parser = (isFunction(field.parser)) ? 349 field.parser : 350 Y.Parsers[field.parser + '']; 351 352 if (parser) { 353 fieldParsers.push({ 354 key : key, 355 parser: parser 356 }); 357 } 358 } 359 360 // Traverse list of array_in, creating records of simple fields, 361 // complex fields, and applying parsers as necessary 362 for (i=array_in.length-1; i>=0; --i) { 363 record = {}; 364 result = array_in[i]; 365 if(result) { 366 // Cycle through complexLocators 367 for (j=complexPaths.length - 1; j>=0; --j) { 368 path = complexPaths[j]; 369 val = SchemaJSON.getLocationValue(path.path, result); 370 if (val === undefined) { 371 val = SchemaJSON.getLocationValue([path.locator], result); 372 // Fail over keys like "foo.bar" from nested parsing 373 // to single token parsing if a value is found in 374 // results["foo.bar"] 375 if (val !== undefined) { 376 simplePaths.push({ 377 key: path.key, 378 path: path.locator 379 }); 380 // Don't try to process the path as complex 381 // for further results 382 complexPaths.splice(i,1); 383 continue; 384 } 385 } 386 387 record[path.key] = Base.parse.call(this, 388 (SchemaJSON.getLocationValue(path.path, result)), path); 389 } 390 391 // Cycle through simpleLocators 392 for (j = simplePaths.length - 1; j >= 0; --j) { 393 path = simplePaths[j]; 394 // Bug 1777850: The result might be an array instead of object 395 record[path.key] = Base.parse.call(this, 396 ((result[path.path] === undefined) ? 397 result[j] : result[path.path]), path); 398 } 399 400 // Cycle through fieldParsers 401 for (j=fieldParsers.length-1; j>=0; --j) { 402 key = fieldParsers[j].key; 403 record[key] = fieldParsers[j].parser.call(this, record[key]); 404 // Safety net 405 if (record[key] === undefined) { 406 record[key] = null; 407 } 408 } 409 results[i] = record; 410 } 411 } 412 data_out.results = results; 413 return data_out; 414 }, 415 416 /** 417 * Parses results data according to schema 418 * 419 * @method _parseMeta 420 * @param metaFields {Object} Metafields definitions. 421 * @param json_in {Object} JSON to parse. 422 * @param data_out {Object} In-progress parsed data to update. 423 * @return {Object} Schema-parsed meta data. 424 * @static 425 * @protected 426 */ 427 _parseMeta: function(metaFields, json_in, data_out) { 428 if (isObject(metaFields)) { 429 var key, path; 430 for(key in metaFields) { 431 if (metaFields.hasOwnProperty(key)) { 432 path = SchemaJSON.getPath(metaFields[key]); 433 if (path && json_in) { 434 data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in); 435 } 436 } 437 } 438 } 439 else { 440 data_out.error = new Error("JSON meta data retrieval failure"); 441 } 442 return data_out; 443 } 444 }; 445 446 // TODO: Y.Object + mix() might be better here 447 Y.DataSchema.JSON = Y.mix(SchemaJSON, Base); 448 449 450 }, '3.17.2', {"requires": ["dataschema-base", "json"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |