2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('dataschema-json', function(Y) {
11 * Provides a DataSchema implementation which can be used to work with JSON data.
14 * @submodule dataschema-json
18 * JSON subclass for the DataSchema Utility.
19 * @class DataSchema.JSON
20 * @extends DataSchema.Base
27 /////////////////////////////////////////////////////////////////////////////
29 // DataSchema.JSON static methods
31 /////////////////////////////////////////////////////////////////////////////
33 * Utility function converts JSON locator strings into walkable paths
35 * @method DataSchema.JSON.getPath
36 * @param locator {String} JSON value locator.
37 * @return {String[]} Walkable path to data value.
40 getPath: function(locator) {
46 // Strip the ["string keys"] and [1] array indexes
48 replace(/\[(['"])(.*?)\1\]/g,
49 function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
51 function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
52 replace(/^\./,''); // remove leading dot
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)];
70 * Utility function to walk a path and return the value located there.
72 * @method DataSchema.JSON.getLocationValue
73 * @param path {String[]} Locator path.
74 * @param data {String} Data to traverse.
75 * @return {Object} Data value at location.
78 getLocationValue: function (path, data) {
83 LANG.isObject(data) &&
97 * Applies a given schema to given JSON data.
100 * @param schema {Object} Schema to apply.
101 * @param data {Object} JSON data.
102 * @return {Object} Schema-parsed data.
105 apply: function(schema, data) {
107 data_out = {results:[],meta:{}};
109 // Convert incoming JSON strings
110 if(!LANG.isObject(data)) {
112 data_in = Y.JSON.parse(data);
120 if(LANG.isObject(data_in) && schema) {
121 // Parse results data
122 if(!LANG.isUndefined(schema.resultListLocator)) {
123 data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out);
127 if(!LANG.isUndefined(schema.metaFields)) {
128 data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out);
132 data_out.error = new Error("JSON schema parse failure");
139 * Schema-parsed list of results from full data
141 * @method _parseResults
142 * @param schema {Object} Schema to parse against.
143 * @param json_in {Object} JSON to parse.
144 * @param data_out {Object} In-progress parsed data to update.
145 * @return {Object} Parsed data object.
149 _parseResults: function(schema, json_in, data_out) {
154 if(schema.resultListLocator) {
155 path = SchemaJSON.getPath(schema.resultListLocator);
157 results = SchemaJSON.getLocationValue(path, json_in);
158 if (results === undefined) {
159 data_out.results = [];
160 error = new Error("JSON results retrieval failure");
163 if(LANG.isArray(results)) {
164 // if no result fields are passed in, then just take the results array whole-hog
165 // Sometimes you're getting an array of strings, or want the whole object,
166 // so resultFields don't make sense.
167 if (LANG.isArray(schema.resultFields)) {
168 data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
171 data_out.results = results;
175 data_out.results = [];
176 error = new Error("JSON Schema fields retrieval failure");
181 error = new Error("JSON Schema results locator failure");
185 data_out.error = error;
193 * Get field data values out of list of full results
195 * @method _getFieldValues
196 * @param fields {Array} Fields to find.
197 * @param array_in {Array} Results to parse.
198 * @param data_out {Object} In-progress parsed data to update.
199 * @return {Object} Parsed data object.
203 _getFieldValues: function(fields, array_in, data_out) {
207 field, key, locator, path, parser,
208 simplePaths = [], complexPaths = [], fieldParsers = [],
211 // First collect hashes of simple paths, complex paths, and parsers
212 for (i=0; i<len; i++) {
213 field = fields[i]; // A field can be a simple string or a hash
214 key = field.key || field; // Find the key
215 locator = field.locator || key; // Find the locator
217 // Validate and store locators for later
218 path = SchemaJSON.getPath(locator);
220 if (path.length === 1) {
221 simplePaths[simplePaths.length] = {key:key, path:path[0]};
223 complexPaths[complexPaths.length] = {key:key, path:path};
228 // Validate and store parsers for later
229 //TODO: use Y.DataSchema.parse?
230 parser = (LANG.isFunction(field.parser)) ? field.parser : Y.Parsers[field.parser+''];
232 fieldParsers[fieldParsers.length] = {key:key, parser:parser};
236 // Traverse list of array_in, creating records of simple fields,
237 // complex fields, and applying parsers as necessary
238 for (i=array_in.length-1; i>=0; --i) {
240 result = array_in[i];
242 // Cycle through simpleLocators
243 for (j=simplePaths.length-1; j>=0; --j) {
244 // Bug 1777850: The result might be an array instead of object
245 record[simplePaths[j].key] = Y.DataSchema.Base.parse.call(this,
246 (LANG.isUndefined(result[simplePaths[j].path]) ?
247 result[j] : result[simplePaths[j].path]), simplePaths[j]);
250 // Cycle through complexLocators
251 for (j=complexPaths.length - 1; j>=0; --j) {
252 record[complexPaths[j].key] = Y.DataSchema.Base.parse.call(this,
253 (SchemaJSON.getLocationValue(complexPaths[j].path, result)), complexPaths[j] );
256 // Cycle through fieldParsers
257 for (j=fieldParsers.length-1; j>=0; --j) {
258 key = fieldParsers[j].key;
259 record[key] = fieldParsers[j].parser.call(this, record[key]);
261 if (LANG.isUndefined(record[key])) {
268 data_out.results = results;
273 * Parses results data according to schema
276 * @param metaFields {Object} Metafields definitions.
277 * @param json_in {Object} JSON to parse.
278 * @param data_out {Object} In-progress parsed data to update.
279 * @return {Object} Schema-parsed meta data.
283 _parseMeta: function(metaFields, json_in, data_out) {
284 if(LANG.isObject(metaFields)) {
286 for(key in metaFields) {
287 if (metaFields.hasOwnProperty(key)) {
288 path = SchemaJSON.getPath(metaFields[key]);
289 if (path && json_in) {
290 data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
296 data_out.error = new Error("JSON meta data retrieval failure");
302 Y.DataSchema.JSON = Y.mix(SchemaJSON, Y.DataSchema.Base);
306 }, '3.3.0' ,{requires:['dataschema-base','json']});