Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
                Y.log("Invalid locator: " + locator, "error", "dataschema-json");
74
            }
75
            */
76
        }
77
        return path;
78
    },
79
 
80
    /**
81
     * Utility function to walk a path and return the value located there.
82
     *
83
     * @method getLocationValue
84
     * @param path {String[]} Locator path.
85
     * @param data {String} Data to traverse.
86
     * @return {Object} Data value at location.
87
     * @static
88
     */
89
    getLocationValue: function (path, data) {
90
        var i = 0,
91
            len = path.length;
92
        for (;i<len;i++) {
93
            if (isObject(data) && (path[i] in data)) {
94
                data = data[path[i]];
95
            } else {
96
                data = undefined;
97
                break;
98
            }
99
        }
100
        return data;
101
    },
102
 
103
    /**
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.
109
 
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()`.
112
 
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
119
    locator"
120
 
121
    Field data in the result list is extracted with field identifiers in
122
    _schema.resultFields_.  Field identifiers are objects with the following
123
    properties:
124
 
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
129
            return a value.
130
 
131
    If no value parsing is needed, you can use path locators (strings)
132
    instead of field identifiers (objects) -- see example below.
133
 
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.
136
 
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
140
    values.
141
 
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.
144
 
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`.
148
 
149
 
150
    @example
151
        // Process array of arrays
152
        var schema = {
153
                resultListLocator: 'produce.fruit',
154
                resultFields: [ 'name', 'color' ]
155
            },
156
            data = {
157
                produce: {
158
                    fruit: [
159
                        [ 'Banana', 'yellow' ],
160
                        [ 'Orange', 'orange' ],
161
                        [ 'Eggplant', 'purple' ]
162
                    ]
163
                }
164
            };
165
 
166
        var response = Y.DataSchema.JSON.apply(schema, data);
167
 
168
        // response.results[0] is { name: "Banana", color: "yellow" }
169
 
170
 
171
        // Process array of objects + some metadata
172
        schema.metaFields = [ 'lastInventory' ];
173
 
174
        data = {
175
            produce: {
176
                fruit: [
177
                    { name: 'Banana', color: 'yellow', price: '1.96' },
178
                    { name: 'Orange', color: 'orange', price: '2.04' },
179
                    { name: 'Eggplant', color: 'purple', price: '4.31' }
180
                ]
181
            },
182
            lastInventory: '2011-07-19'
183
        };
184
 
185
        response = Y.DataSchema.JSON.apply(schema, data);
186
 
187
        // response.results[0] is { name: "Banana", color: "yellow" }
188
        // response.meta.lastInventory is '2001-07-19'
189
 
190
 
191
        // Use parsers
192
        schema.resultFields = [
193
            {
194
                key: 'name',
195
                parser: function (val) { return val.toUpperCase(); }
196
            },
197
            {
198
                key: 'price',
199
                parser: 'number' // Uses Y.Parsers.number
200
            }
201
        ];
202
 
203
        response = Y.DataSchema.JSON.apply(schema, data);
204
 
205
        // Note price was converted from a numeric string to a number
206
        // response.results[0] looks like { fruit: "BANANA", price: 1.96 }
207
 
208
    @method apply
209
    @param {Object} [schema] Schema to apply.  Supported configuration
210
        properties are:
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
215
          details.
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`
220
    @static
221
    **/
222
    apply: function(schema, data) {
223
        var data_in = data,
224
            data_out = { results: [], meta: {} };
225
 
226
        // Convert incoming JSON strings
227
        if (!isObject(data)) {
228
            try {
229
                data_in = Y.JSON.parse(data);
230
            }
231
            catch(e) {
232
                data_out.error = e;
233
                return data_out;
234
            }
235
        }
236
 
237
        if (isObject(data_in) && schema) {
238
            // Parse results data
239
            data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out);
240
 
241
            // Parse meta data
242
            if (schema.metaFields !== undefined) {
243
                data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out);
244
            }
245
        }
246
        else {
247
            Y.log("JSON data could not be schema-parsed: " + Y.dump(data) + " " + Y.dump(data), "error", "dataschema-json");
248
            data_out.error = new Error("JSON schema parse failure");
249
        }
250
 
251
        return data_out;
252
    },
253
 
254
    /**
255
     * Schema-parsed list of results from full data
256
     *
257
     * @method _parseResults
258
     * @param schema {Object} Schema to parse against.
259
     * @param json_in {Object} JSON to parse.
260
     * @param data_out {Object} In-progress parsed data to update.
261
     * @return {Object} Parsed data object.
262
     * @static
263
     * @protected
264
     */
265
    _parseResults: function(schema, json_in, data_out) {
266
        var getPath  = SchemaJSON.getPath,
267
            getValue = SchemaJSON.getLocationValue,
268
            path     = getPath(schema.resultListLocator),
269
            results  = path ?
270
                        (getValue(path, json_in) ||
271
                         // Fall back to treat resultListLocator as a simple key
272
                            json_in[schema.resultListLocator]) :
273
                        // Or if no resultListLocator is supplied, use the input
274
                        json_in;
275
 
276
        if (isArray(results)) {
277
            // if no result fields are passed in, then just take
278
            // the results array whole-hog Sometimes you're getting
279
            // an array of strings, or want the whole object, so
280
            // resultFields don't make sense.
281
            if (isArray(schema.resultFields)) {
282
                data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
283
            } else {
284
                data_out.results = results;
285
            }
286
        } else if (schema.resultListLocator) {
287
            data_out.results = [];
288
            data_out.error = new Error("JSON results retrieval failure");
289
            Y.log("JSON data could not be parsed: " + Y.dump(json_in), "error", "dataschema-json");
290
        }
291
 
292
        return data_out;
293
    },
294
 
295
    /**
296
     * Get field data values out of list of full results
297
     *
298
     * @method _getFieldValues
299
     * @param fields {Array} Fields to find.
300
     * @param array_in {Array} Results to parse.
301
     * @param data_out {Object} In-progress parsed data to update.
302
     * @return {Object} Parsed data object.
303
     * @static
304
     * @protected
305
     */
306
    _getFieldValues: function(fields, array_in, data_out) {
307
        var results = [],
308
            len = fields.length,
309
            i, j,
310
            field, key, locator, path, parser, val,
311
            simplePaths = [], complexPaths = [], fieldParsers = [],
312
            result, record;
313
 
314
        // First collect hashes of simple paths, complex paths, and parsers
315
        for (i=0; i<len; i++) {
316
            field = fields[i]; // A field can be a simple string or a hash
317
            key = field.key || field; // Find the key
318
            locator = field.locator || key; // Find the locator
319
 
320
            // Validate and store locators for later
321
            path = SchemaJSON.getPath(locator);
322
            if (path) {
323
                if (path.length === 1) {
324
                    simplePaths.push({
325
                        key : key,
326
                        path: path[0]
327
                    });
328
                } else {
329
                    complexPaths.push({
330
                        key    : key,
331
                        path   : path,
332
                        locator: locator
333
                    });
334
                }
335
            } else {
336
                Y.log("Invalid key syntax: " + key, "warn", "dataschema-json");
337
            }
338
 
339
            // Validate and store parsers for later
340
            //TODO: use Y.DataSchema.parse?
341
            parser = (isFunction(field.parser)) ?
342
                        field.parser :
343
                        Y.Parsers[field.parser + ''];
344
 
345
            if (parser) {
346
                fieldParsers.push({
347
                    key   : key,
348
                    parser: parser
349
                });
350
            }
351
        }
352
 
353
        // Traverse list of array_in, creating records of simple fields,
354
        // complex fields, and applying parsers as necessary
355
        for (i=array_in.length-1; i>=0; --i) {
356
            record = {};
357
            result = array_in[i];
358
            if(result) {
359
                // Cycle through complexLocators
360
                for (j=complexPaths.length - 1; j>=0; --j) {
361
                    path = complexPaths[j];
362
                    val = SchemaJSON.getLocationValue(path.path, result);
363
                    if (val === undefined) {
364
                        val = SchemaJSON.getLocationValue([path.locator], result);
365
                        // Fail over keys like "foo.bar" from nested parsing
366
                        // to single token parsing if a value is found in
367
                        // results["foo.bar"]
368
                        if (val !== undefined) {
369
                            simplePaths.push({
370
                                key:  path.key,
371
                                path: path.locator
372
                            });
373
                            // Don't try to process the path as complex
374
                            // for further results
375
                            complexPaths.splice(i,1);
376
                            continue;
377
                        }
378
                    }
379
 
380
                    record[path.key] = Base.parse.call(this,
381
                        (SchemaJSON.getLocationValue(path.path, result)), path);
382
                }
383
 
384
                // Cycle through simpleLocators
385
                for (j = simplePaths.length - 1; j >= 0; --j) {
386
                    path = simplePaths[j];
387
                    // Bug 1777850: The result might be an array instead of object
388
                    record[path.key] = Base.parse.call(this,
389
                            ((result[path.path] === undefined) ?
390
                            result[j] : result[path.path]), path);
391
                }
392
 
393
                // Cycle through fieldParsers
394
                for (j=fieldParsers.length-1; j>=0; --j) {
395
                    key = fieldParsers[j].key;
396
                    record[key] = fieldParsers[j].parser.call(this, record[key]);
397
                    // Safety net
398
                    if (record[key] === undefined) {
399
                        record[key] = null;
400
                    }
401
                }
402
                results[i] = record;
403
            }
404
        }
405
        data_out.results = results;
406
        return data_out;
407
    },
408
 
409
    /**
410
     * Parses results data according to schema
411
     *
412
     * @method _parseMeta
413
     * @param metaFields {Object} Metafields definitions.
414
     * @param json_in {Object} JSON to parse.
415
     * @param data_out {Object} In-progress parsed data to update.
416
     * @return {Object} Schema-parsed meta data.
417
     * @static
418
     * @protected
419
     */
420
    _parseMeta: function(metaFields, json_in, data_out) {
421
        if (isObject(metaFields)) {
422
            var key, path;
423
            for(key in metaFields) {
424
                if (metaFields.hasOwnProperty(key)) {
425
                    path = SchemaJSON.getPath(metaFields[key]);
426
                    if (path && json_in) {
427
                        data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
428
                    }
429
                }
430
            }
431
        }
432
        else {
433
            data_out.error = new Error("JSON meta data retrieval failure");
434
        }
435
        return data_out;
436
    }
437
};
438
 
439
// TODO: Y.Object + mix() might be better here
440
Y.DataSchema.JSON = Y.mix(SchemaJSON, Base);
441
 
442
 
443
}, '3.18.1', {"requires": ["dataschema-base", "json"]});