src/cm/media/js/lib/yui/yui_3.10.3/build/dataschema-json/dataschema-json.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 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             }
       
    81             */
       
    82         }
       
    83         return path;
       
    84     },
       
    85 
       
    86     /**
       
    87      * Utility function to walk a path and return the value located there.
       
    88      *
       
    89      * @method getLocationValue
       
    90      * @param path {String[]} Locator path.
       
    91      * @param data {String} Data to traverse.
       
    92      * @return {Object} Data value at location.
       
    93      * @static
       
    94      */
       
    95     getLocationValue: function (path, data) {
       
    96         var i = 0,
       
    97             len = path.length;
       
    98         for (;i<len;i++) {
       
    99             if (isObject(data) && (path[i] in data)) {
       
   100                 data = data[path[i]];
       
   101             } else {
       
   102                 data = undefined;
       
   103                 break;
       
   104             }
       
   105         }
       
   106         return data;
       
   107     },
       
   108 
       
   109     /**
       
   110     Applies a schema to an array of data located in a JSON structure, returning
       
   111     a normalized object with results in the `results` property. Additional
       
   112     information can be parsed out of the JSON for inclusion in the `meta`
       
   113     property of the response object.  If an error is encountered during
       
   114     processing, an `error` property will be added.
       
   115 
       
   116     The input _data_ is expected to be an object or array.  If it is a string,
       
   117     it will be passed through `Y.JSON.parse()`.
       
   118 
       
   119     If _data_ contains an array of data records to normalize, specify the
       
   120     _schema.resultListLocator_ as a dot separated path string just as you would
       
   121     reference it in JavaScript.  So if your _data_ object has a record array at
       
   122     _data.response.results_, use _schema.resultListLocator_ =
       
   123     "response.results". Bracket notation can also be used for array indices or
       
   124     object properties (e.g. "response['results']");  This is called a "path
       
   125     locator"
       
   126 
       
   127     Field data in the result list is extracted with field identifiers in
       
   128     _schema.resultFields_.  Field identifiers are objects with the following
       
   129     properties:
       
   130 
       
   131       * `key`   : <strong>(required)</strong> The path locator (String)
       
   132       * `parser`: A function or the name of a function on `Y.Parsers` used
       
   133             to convert the input value into a normalized type.  Parser
       
   134             functions are passed the value as input and are expected to
       
   135             return a value.
       
   136 
       
   137     If no value parsing is needed, you can use path locators (strings) 
       
   138     instead of field identifiers (objects) -- see example below.
       
   139 
       
   140     If no processing of the result list array is needed, _schema.resultFields_
       
   141     can be omitted; the `response.results` will point directly to the array.
       
   142 
       
   143     If the result list contains arrays, `response.results` will contain an
       
   144     array of objects with key:value pairs assuming the fields in
       
   145     _schema.resultFields_ are ordered in accordance with the data array
       
   146     values.
       
   147 
       
   148     If the result list contains objects, the identified _schema.resultFields_
       
   149     will be used to extract a value from those objects for the output result.
       
   150 
       
   151     To extract additional information from the JSON, include an array of
       
   152     path locators in _schema.metaFields_.  The collected values will be
       
   153     stored in `response.meta`.
       
   154 
       
   155 
       
   156     @example
       
   157         // Process array of arrays
       
   158         var schema = {
       
   159                 resultListLocator: 'produce.fruit',
       
   160                 resultFields: [ 'name', 'color' ]
       
   161             },
       
   162             data = {
       
   163                 produce: {
       
   164                     fruit: [
       
   165                         [ 'Banana', 'yellow' ],
       
   166                         [ 'Orange', 'orange' ],
       
   167                         [ 'Eggplant', 'purple' ]
       
   168                     ]
       
   169                 }
       
   170             };
       
   171 
       
   172         var response = Y.DataSchema.JSON.apply(schema, data);
       
   173 
       
   174         // response.results[0] is { name: "Banana", color: "yellow" }
       
   175 
       
   176         
       
   177         // Process array of objects + some metadata
       
   178         schema.metaFields = [ 'lastInventory' ];
       
   179 
       
   180         data = {
       
   181             produce: {
       
   182                 fruit: [
       
   183                     { name: 'Banana', color: 'yellow', price: '1.96' },
       
   184                     { name: 'Orange', color: 'orange', price: '2.04' },
       
   185                     { name: 'Eggplant', color: 'purple', price: '4.31' }
       
   186                 ]
       
   187             },
       
   188             lastInventory: '2011-07-19'
       
   189         };
       
   190 
       
   191         response = Y.DataSchema.JSON.apply(schema, data);
       
   192 
       
   193         // response.results[0] is { name: "Banana", color: "yellow" }
       
   194         // response.meta.lastInventory is '2001-07-19'
       
   195 
       
   196 
       
   197         // Use parsers
       
   198         schema.resultFields = [
       
   199             {
       
   200                 key: 'name',
       
   201                 parser: function (val) { return val.toUpperCase(); }
       
   202             },
       
   203             {
       
   204                 key: 'price',
       
   205                 parser: 'number' // Uses Y.Parsers.number
       
   206             }
       
   207         ];
       
   208 
       
   209         response = Y.DataSchema.JSON.apply(schema, data);
       
   210 
       
   211         // Note price was converted from a numeric string to a number
       
   212         // response.results[0] looks like { fruit: "BANANA", price: 1.96 }
       
   213      
       
   214     @method apply
       
   215     @param {Object} [schema] Schema to apply.  Supported configuration
       
   216         properties are:
       
   217       @param {String} [schema.resultListLocator] Path locator for the
       
   218           location of the array of records to flatten into `response.results`
       
   219       @param {Array} [schema.resultFields] Field identifiers to
       
   220           locate/assign values in the response records. See above for
       
   221           details.
       
   222       @param {Array} [schema.metaFields] Path locators to extract extra
       
   223           non-record related information from the data object.
       
   224     @param {Object|Array|String} data JSON data or its string serialization.
       
   225     @return {Object} An Object with properties `results` and `meta`
       
   226     @static
       
   227     **/
       
   228     apply: function(schema, data) {
       
   229         var data_in = data,
       
   230             data_out = { results: [], meta: {} };
       
   231 
       
   232         // Convert incoming JSON strings
       
   233         if (!isObject(data)) {
       
   234             try {
       
   235                 data_in = Y.JSON.parse(data);
       
   236             }
       
   237             catch(e) {
       
   238                 data_out.error = e;
       
   239                 return data_out;
       
   240             }
       
   241         }
       
   242 
       
   243         if (isObject(data_in) && schema) {
       
   244             // Parse results data
       
   245             data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out);
       
   246 
       
   247             // Parse meta data
       
   248             if (schema.metaFields !== undefined) {
       
   249                 data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out);
       
   250             }
       
   251         }
       
   252         else {
       
   253             data_out.error = new Error("JSON schema parse failure");
       
   254         }
       
   255 
       
   256         return data_out;
       
   257     },
       
   258 
       
   259     /**
       
   260      * Schema-parsed list of results from full data
       
   261      *
       
   262      * @method _parseResults
       
   263      * @param schema {Object} Schema to parse against.
       
   264      * @param json_in {Object} JSON to parse.
       
   265      * @param data_out {Object} In-progress parsed data to update.
       
   266      * @return {Object} Parsed data object.
       
   267      * @static
       
   268      * @protected
       
   269      */
       
   270     _parseResults: function(schema, json_in, data_out) {
       
   271         var getPath  = SchemaJSON.getPath,
       
   272             getValue = SchemaJSON.getLocationValue,
       
   273             path     = getPath(schema.resultListLocator),
       
   274             results  = path ?
       
   275                         (getValue(path, json_in) ||
       
   276                          // Fall back to treat resultListLocator as a simple key
       
   277                             json_in[schema.resultListLocator]) :
       
   278                         // Or if no resultListLocator is supplied, use the input
       
   279                         json_in;
       
   280 
       
   281         if (isArray(results)) {
       
   282             // if no result fields are passed in, then just take
       
   283             // the results array whole-hog Sometimes you're getting
       
   284             // an array of strings, or want the whole object, so
       
   285             // resultFields don't make sense.
       
   286             if (isArray(schema.resultFields)) {
       
   287                 data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
       
   288             } else {
       
   289                 data_out.results = results;
       
   290             }
       
   291         } else if (schema.resultListLocator) {
       
   292             data_out.results = [];
       
   293             data_out.error = new Error("JSON results retrieval failure");
       
   294         }
       
   295 
       
   296         return data_out;
       
   297     },
       
   298 
       
   299     /**
       
   300      * Get field data values out of list of full results
       
   301      *
       
   302      * @method _getFieldValues
       
   303      * @param fields {Array} Fields to find.
       
   304      * @param array_in {Array} Results to parse.
       
   305      * @param data_out {Object} In-progress parsed data to update.
       
   306      * @return {Object} Parsed data object.
       
   307      * @static
       
   308      * @protected
       
   309      */
       
   310     _getFieldValues: function(fields, array_in, data_out) {
       
   311         var results = [],
       
   312             len = fields.length,
       
   313             i, j,
       
   314             field, key, locator, path, parser, val,
       
   315             simplePaths = [], complexPaths = [], fieldParsers = [],
       
   316             result, record;
       
   317 
       
   318         // First collect hashes of simple paths, complex paths, and parsers
       
   319         for (i=0; i<len; i++) {
       
   320             field = fields[i]; // A field can be a simple string or a hash
       
   321             key = field.key || field; // Find the key
       
   322             locator = field.locator || key; // Find the locator
       
   323 
       
   324             // Validate and store locators for later
       
   325             path = SchemaJSON.getPath(locator);
       
   326             if (path) {
       
   327                 if (path.length === 1) {
       
   328                     simplePaths.push({
       
   329                         key : key,
       
   330                         path: path[0]
       
   331                     });
       
   332                 } else {
       
   333                     complexPaths.push({
       
   334                         key    : key,
       
   335                         path   : path,
       
   336                         locator: locator
       
   337                     });
       
   338                 }
       
   339             } else {
       
   340             }
       
   341 
       
   342             // Validate and store parsers for later
       
   343             //TODO: use Y.DataSchema.parse?
       
   344             parser = (isFunction(field.parser)) ?
       
   345                         field.parser :
       
   346                         Y.Parsers[field.parser + ''];
       
   347 
       
   348             if (parser) {
       
   349                 fieldParsers.push({
       
   350                     key   : key,
       
   351                     parser: parser
       
   352                 });
       
   353             }
       
   354         }
       
   355 
       
   356         // Traverse list of array_in, creating records of simple fields,
       
   357         // complex fields, and applying parsers as necessary
       
   358         for (i=array_in.length-1; i>=0; --i) {
       
   359             record = {};
       
   360             result = array_in[i];
       
   361             if(result) {
       
   362                 // Cycle through complexLocators
       
   363                 for (j=complexPaths.length - 1; j>=0; --j) {
       
   364                     path = complexPaths[j];
       
   365                     val = SchemaJSON.getLocationValue(path.path, result);
       
   366                     if (val === undefined) {
       
   367                         val = SchemaJSON.getLocationValue([path.locator], result);
       
   368                         // Fail over keys like "foo.bar" from nested parsing
       
   369                         // to single token parsing if a value is found in
       
   370                         // results["foo.bar"]
       
   371                         if (val !== undefined) {
       
   372                             simplePaths.push({
       
   373                                 key:  path.key,
       
   374                                 path: path.locator
       
   375                             });
       
   376                             // Don't try to process the path as complex
       
   377                             // for further results
       
   378                             complexPaths.splice(i,1);
       
   379                             continue;
       
   380                         }
       
   381                     }
       
   382 
       
   383                     record[path.key] = Base.parse.call(this,
       
   384                         (SchemaJSON.getLocationValue(path.path, result)), path);
       
   385                 }
       
   386 
       
   387                 // Cycle through simpleLocators
       
   388                 for (j = simplePaths.length - 1; j >= 0; --j) {
       
   389                     path = simplePaths[j];
       
   390                     // Bug 1777850: The result might be an array instead of object
       
   391                     record[path.key] = Base.parse.call(this,
       
   392                             ((result[path.path] === undefined) ?
       
   393                             result[j] : result[path.path]), path);
       
   394                 }
       
   395 
       
   396                 // Cycle through fieldParsers
       
   397                 for (j=fieldParsers.length-1; j>=0; --j) {
       
   398                     key = fieldParsers[j].key;
       
   399                     record[key] = fieldParsers[j].parser.call(this, record[key]);
       
   400                     // Safety net
       
   401                     if (record[key] === undefined) {
       
   402                         record[key] = null;
       
   403                     }
       
   404                 }
       
   405                 results[i] = record;
       
   406             }
       
   407         }
       
   408         data_out.results = results;
       
   409         return data_out;
       
   410     },
       
   411 
       
   412     /**
       
   413      * Parses results data according to schema
       
   414      *
       
   415      * @method _parseMeta
       
   416      * @param metaFields {Object} Metafields definitions.
       
   417      * @param json_in {Object} JSON to parse.
       
   418      * @param data_out {Object} In-progress parsed data to update.
       
   419      * @return {Object} Schema-parsed meta data.
       
   420      * @static
       
   421      * @protected
       
   422      */
       
   423     _parseMeta: function(metaFields, json_in, data_out) {
       
   424         if (isObject(metaFields)) {
       
   425             var key, path;
       
   426             for(key in metaFields) {
       
   427                 if (metaFields.hasOwnProperty(key)) {
       
   428                     path = SchemaJSON.getPath(metaFields[key]);
       
   429                     if (path && json_in) {
       
   430                         data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
       
   431                     }
       
   432                 }
       
   433             }
       
   434         }
       
   435         else {
       
   436             data_out.error = new Error("JSON meta data retrieval failure");
       
   437         }
       
   438         return data_out;
       
   439     }
       
   440 };
       
   441 
       
   442 // TODO: Y.Object + mix() might be better here
       
   443 Y.DataSchema.JSON = Y.mix(SchemaJSON, Base);
       
   444 
       
   445 
       
   446 }, '3.10.3', {"requires": ["dataschema-base", "json"]});