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