1
/* YUI 3.9.1 (build 5852) Copyright 2013 Yahoo! Inc. http://yuilibrary.com/license/ */
2
YUI.add('dataschema-json', function (Y, NAME) {
5
Provides a DataSchema implementation which can be used to work with JSON data.
8
@submodule dataschema-json
12
Provides a DataSchema implementation which can be used to work with JSON data.
14
See the `apply` method for usage.
16
@class DataSchema.JSON
17
@extends DataSchema.Base
21
isFunction = LANG.isFunction,
22
isObject = LANG.isObject,
23
isArray = LANG.isArray,
24
// TODO: I don't think the calls to Base.* need to be done via Base since
25
// Base is mixed into SchemaJSON. Investigate for later.
26
Base = Y.DataSchema.Base,
32
/////////////////////////////////////////////////////////////////////////////
34
// DataSchema.JSON static methods
36
/////////////////////////////////////////////////////////////////////////////
38
* Utility function converts JSON locator strings into walkable paths
41
* @param locator {String} JSON value locator.
42
* @return {String[]} Walkable path to data value.
45
getPath: function(locator) {
51
// Strip the ["string keys"] and [1] array indexes
52
// TODO: the first two steps can probably be reduced to one with
53
// /\[\s*(['"])?(.*?)\1\s*\]/g, but the array indices would be
54
// stored as strings. This is not likely an issue.
56
replace(/\[\s*(['"])(.*?)\1\s*\]/g,
57
function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
59
function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
60
replace(/^\./,''); // remove leading dot
62
// Validate against problematic characters.
63
// commented out because the path isn't sent to eval, so it
64
// should be safe. I'm not sure what makes a locator invalid.
65
//if (!/[^\w\.\$@]/.test(locator)) {
66
path = locator.split('.');
67
for (i=path.length-1; i >= 0; --i) {
68
if (path[i].charAt(0) === '@') {
69
path[i] = keys[parseInt(path[i].substr(1),10)];
81
* Utility function to walk a path and return the value located there.
83
* @method getLocationValue
84
* @param path {String[]} Locator path.
85
* @param data {String} Data to traverse.
86
* @return {Object} Data value at location.
89
getLocationValue: function (path, data) {
93
if (isObject(data) && (path[i] in data)) {
104
Applies a schema to an array of data located in a JSON structure, returning
105
a normalized object with results in the `results` property. Additional
106
information can be parsed out of the JSON for inclusion in the `meta`
107
property of the response object. If an error is encountered during
108
processing, an `error` property will be added.
110
The input _data_ is expected to be an object or array. If it is a string,
111
it will be passed through `Y.JSON.parse()`.
113
If _data_ contains an array of data records to normalize, specify the
114
_schema.resultListLocator_ as a dot separated path string just as you would
115
reference it in JavaScript. So if your _data_ object has a record array at
116
_data.response.results_, use _schema.resultListLocator_ =
117
"response.results". Bracket notation can also be used for array indices or
118
object properties (e.g. "response['results']"); This is called a "path
121
Field data in the result list is extracted with field identifiers in
122
_schema.resultFields_. Field identifiers are objects with the following
125
* `key` : <strong>(required)</strong> The path locator (String)
126
* `parser`: A function or the name of a function on `Y.Parsers` used
127
to convert the input value into a normalized type. Parser
128
functions are passed the value as input and are expected to
131
If no value parsing is needed, you can use path locators (strings)
132
instead of field identifiers (objects) -- see example below.
134
If no processing of the result list array is needed, _schema.resultFields_
135
can be omitted; the `response.results` will point directly to the array.
137
If the result list contains arrays, `response.results` will contain an
138
array of objects with key:value pairs assuming the fields in
139
_schema.resultFields_ are ordered in accordance with the data array
142
If the result list contains objects, the identified _schema.resultFields_
143
will be used to extract a value from those objects for the output result.
145
To extract additional information from the JSON, include an array of
146
path locators in _schema.metaFields_. The collected values will be
147
stored in `response.meta`.
151
// Process array of arrays
153
resultListLocator: 'produce.fruit',
154
resultFields: [ 'name', 'color' ]
159
[ 'Banana', 'yellow' ],
160
[ 'Orange', 'orange' ],
161
[ 'Eggplant', 'purple' ]
166
var response = Y.DataSchema.JSON.apply(schema, data);
168
// response.results[0] is { name: "Banana", color: "yellow" }
171
// Process array of objects + some metadata
172
schema.metaFields = [ 'lastInventory' ];
177
{ name: 'Banana', color: 'yellow', price: '1.96' },
178
{ name: 'Orange', color: 'orange', price: '2.04' },
179
{ name: 'Eggplant', color: 'purple', price: '4.31' }
182
lastInventory: '2011-07-19'
185
response = Y.DataSchema.JSON.apply(schema, data);
187
// response.results[0] is { name: "Banana", color: "yellow" }
188
// response.meta.lastInventory is '2001-07-19'
192
schema.resultFields = [
195
parser: function (val) { return val.toUpperCase(); }
199
parser: 'number' // Uses Y.Parsers.number
203
response = Y.DataSchema.JSON.apply(schema, data);
205
// Note price was converted from a numeric string to a number
206
// response.results[0] looks like { fruit: "BANANA", price: 1.96 }
209
@param {Object} [schema] Schema to apply. Supported configuration
211
@param {String} [schema.resultListLocator] Path locator for the
212
location of the array of records to flatten into `response.results`
213
@param {Array} [schema.resultFields] Field identifiers to
214
locate/assign values in the response records. See above for
216
@param {Array} [schema.metaFields] Path locators to extract extra
217
non-record related information from the data object.
218
@param {Object|Array|String} data JSON data or its string serialization.
219
@return {Object} An Object with properties `results` and `meta`
222
apply: function(schema, data) {
224
data_out = { results: [], meta: {} };
226
// Convert incoming JSON strings
227
if (!isObject(data)) {
229
data_in = Y.JSON.parse(data);
237
if (isObject(data_in) && schema) {
238
// Parse results data
239
data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out);
242
if (schema.metaFields !== undefined) {
243
data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out);
247
data_out.error = new Error("JSON schema parse failure");
254
* Schema-parsed list of results from full data
256
* @method _parseResults
257
* @param schema {Object} Schema to parse against.
258
* @param json_in {Object} JSON to parse.
259
* @param data_out {Object} In-progress parsed data to update.
260
* @return {Object} Parsed data object.
264
_parseResults: function(schema, json_in, data_out) {
265
var getPath = SchemaJSON.getPath,
266
getValue = SchemaJSON.getLocationValue,
267
path = getPath(schema.resultListLocator),
269
(getValue(path, json_in) ||
270
// Fall back to treat resultListLocator as a simple key
271
json_in[schema.resultListLocator]) :
272
// Or if no resultListLocator is supplied, use the input
275
if (isArray(results)) {
276
// if no result fields are passed in, then just take
277
// the results array whole-hog Sometimes you're getting
278
// an array of strings, or want the whole object, so
279
// resultFields don't make sense.
280
if (isArray(schema.resultFields)) {
281
data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
283
data_out.results = results;
285
} else if (schema.resultListLocator) {
286
data_out.results = [];
287
data_out.error = new Error("JSON results retrieval failure");
294
* Get field data values out of list of full results
296
* @method _getFieldValues
297
* @param fields {Array} Fields to find.
298
* @param array_in {Array} Results to parse.
299
* @param data_out {Object} In-progress parsed data to update.
300
* @return {Object} Parsed data object.
304
_getFieldValues: function(fields, array_in, data_out) {
308
field, key, locator, path, parser, val,
309
simplePaths = [], complexPaths = [], fieldParsers = [],
312
// First collect hashes of simple paths, complex paths, and parsers
313
for (i=0; i<len; i++) {
314
field = fields[i]; // A field can be a simple string or a hash
315
key = field.key || field; // Find the key
316
locator = field.locator || key; // Find the locator
318
// Validate and store locators for later
319
path = SchemaJSON.getPath(locator);
321
if (path.length === 1) {
336
// Validate and store parsers for later
337
//TODO: use Y.DataSchema.parse?
338
parser = (isFunction(field.parser)) ?
340
Y.Parsers[field.parser + ''];
350
// Traverse list of array_in, creating records of simple fields,
351
// complex fields, and applying parsers as necessary
352
for (i=array_in.length-1; i>=0; --i) {
354
result = array_in[i];
356
// Cycle through complexLocators
357
for (j=complexPaths.length - 1; j>=0; --j) {
358
path = complexPaths[j];
359
val = SchemaJSON.getLocationValue(path.path, result);
360
if (val === undefined) {
361
val = SchemaJSON.getLocationValue([path.locator], result);
362
// Fail over keys like "foo.bar" from nested parsing
363
// to single token parsing if a value is found in
364
// results["foo.bar"]
365
if (val !== undefined) {
370
// Don't try to process the path as complex
371
// for further results
372
complexPaths.splice(i,1);
377
record[path.key] = Base.parse.call(this,
378
(SchemaJSON.getLocationValue(path.path, result)), path);
381
// Cycle through simpleLocators
382
for (j = simplePaths.length - 1; j >= 0; --j) {
383
path = simplePaths[j];
384
// Bug 1777850: The result might be an array instead of object
385
record[path.key] = Base.parse.call(this,
386
((result[path.path] === undefined) ?
387
result[j] : result[path.path]), path);
390
// Cycle through fieldParsers
391
for (j=fieldParsers.length-1; j>=0; --j) {
392
key = fieldParsers[j].key;
393
record[key] = fieldParsers[j].parser.call(this, record[key]);
395
if (record[key] === undefined) {
402
data_out.results = results;
407
* Parses results data according to schema
410
* @param metaFields {Object} Metafields definitions.
411
* @param json_in {Object} JSON to parse.
412
* @param data_out {Object} In-progress parsed data to update.
413
* @return {Object} Schema-parsed meta data.
417
_parseMeta: function(metaFields, json_in, data_out) {
418
if (isObject(metaFields)) {
420
for(key in metaFields) {
421
if (metaFields.hasOwnProperty(key)) {
422
path = SchemaJSON.getPath(metaFields[key]);
423
if (path && json_in) {
424
data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
430
data_out.error = new Error("JSON meta data retrieval failure");
436
// TODO: Y.Object + mix() might be better here
437
Y.DataSchema.JSON = Y.mix(SchemaJSON, Base);
440
}, '3.9.1', {"requires": ["dataschema-base", "json"]});