UNPKG

foam-framework

Version:
473 lines (382 loc) 12.8 kB
/** * @license * Copyright 2012 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * JSONUtil -- Provides JSON++ marshalling support. * * Like regular JSON, with the following differences: * 1. Marshalls to/from FOAM Objects, rather than maps. * 2. Object Model information is encoded as 'model: "ModelName"' * 3. Default Values are not marshalled, saving disk space and network bandwidth. * 4. Support for marshalling functions. * 5. Support for property filtering, ie. only output non-transient properties. * 6. Support for 'pretty' and 'compact' modes. * * TODO: * Replace with JSONParser.js, when finished. * Maybe rename to FON (FOAM Object Notation, pronounced 'phone') to avoid * confusion with regular JSON syntax. **/ var AbstractFormatter = { keyify: function(str) { return '"' + str + '"'; }, stringify: function(obj) { var buf = ''; this.output(function() { for (var i = 0; i < arguments.length; i++) buf += arguments[i]; }, obj); return buf; }, stringifyObject: function(obj, opt_defaultModel) { var buf = ''; this.outputObject_(function() { for (var i = 0; i < arguments.length; i++) buf += arguments[i]; }, obj, opt_defaultModel); return buf; }, /** @param p a predicate function or an mLang **/ where: function(p) { return { __proto__: this, p: ( p.f && p.f.bind(p) ) || p }; }, p: function() { return true; } }; var JSONUtil = { escape: function(str) { return str .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/[\x00-\x1f]/g, function(c) { return "\\u00" + ((c.charCodeAt(0) < 0x10) ? '0' + c.charCodeAt(0).toString(16) : c.charCodeAt(0).toString(16)); }); }, parseToMap: function(str) { return eval('(' + str + ')'); }, aparse: function(ret, X, str) { var seq = []; var res = this.parse(X, str, seq); if ( seq.length ) { aseq.apply(null, seq)(function() { ret(res); }); return; } ret(res); }, amapToObj: function(ret, X, obj, opt_defaultModel) { var seq = []; var res = this.mapToObj(X, obj, opt_defaultModel, seq); if ( seq.length ) { aseq.apply(null, seq)(function() { ret(res); }); return; } return res; }, parse: function(X, str, seq) { return this.mapToObj(X, this.parseToMap(str), undefined, seq); }, arrayToObjArray: function(X, a, opt_defaultModel, seq) { for ( var i = 0 ; i < a.length ; i++ ) { a[i] = this.mapToObj(X, a[i], opt_defaultModel, seq); } return a; }, /** * Convert JS-Maps which contain the 'model_' property, into * instances of that model. **/ mapToObj: function(X, obj, opt_defaultModel, seq) { if ( ! obj || typeof obj.model_ === 'object' ) return obj; if ( Array.isArray(obj) ) return this.arrayToObjArray(X, obj, undefined, seq); if ( obj instanceof Function ) return obj; if ( obj instanceof Date ) return obj; if ( obj instanceof Object ) { var j = 0; for ( var key in obj ) { if ( key != 'model_' && key != 'prototype_' ) obj[key] = this.mapToObj(X, obj[key], null, seq); j++; } if ( opt_defaultModel && ! obj.model_ ) return opt_defaultModel.create(obj); if ( obj.model_ ) { var newObj = X.lookup(obj.model_); if ( ( ! newObj || ! newObj.finished__ ) ) { var future = afuture(); seq && seq.push(future.get); X.arequire(obj.model_)(function(model) { if ( ! model ) { if ( FLAGS.debug && obj.model_ !== 'Template' && obj.model_ !== 'ArrayProperty' && obj.model_ !== 'ViewFactoryProperty' && obj.model_ !== 'Documentation' && obj.model_ !== 'DocumentationProperty' && obj.model_ !== 'CSSProperty' && obj.model_ !== 'FunctionProperty' ) console.warn('Failed to dynamically load: ', obj.model_); future.set(obj); return; } var tmp = model.create(obj); obj.become(tmp); future.set(obj); }); return obj; } var ret = newObj ? newObj.create(obj, X) : obj; return ret.readResolve ? ret.readResolve() : ret; } return obj } return obj; }, compact: { __proto__: AbstractFormatter, output: function(out, obj, opt_defaultModel) { if ( Array.isArray(obj) ) { this.outputArray_(out, obj); } else if ( typeof obj === 'string' ) { out('"'); out(JSONUtil.escape(obj)); out('"'); } else if ( obj instanceof Function ) { this.outputFunction_(out, obj); } else if ( obj instanceof Date ) { out(obj.getTime()); } else if ( obj instanceof RegExp ) { out(obj.toString()); } else if ( obj instanceof Object ) { if ( obj.model_ && obj.model_.id ) this.outputObject_(out, obj, opt_defaultModel); else this.outputMap_(out, obj); } else if ( typeof obj === 'number' ) { if ( ! isFinite(obj) ) obj = null; out(obj); } else { out(obj === undefined ? null : obj); } }, outputObject_: function(out, obj, opt_defaultModel) { var str = ''; var first = true; out('{'); if ( obj.model_.id !== opt_defaultModel ) { this.outputModel_(out, obj); first = false; } var properties = obj.model_.getRuntimeProperties(); for ( var key in properties ) { var prop = properties[key]; if ( ! this.p(prop, obj) ) continue; if ( prop.name in obj.instance_ ) { var val = obj[prop.name]; if ( Array.isArray(val) && ! val.length ) continue; if ( ! first ) out(','); out(this.keyify(prop.name), ': '); if ( Array.isArray(val) && prop.subType ) { this.outputArray_(out, val, prop.subType); } else { this.output(out, val); } first = false; } } out('}'); }, outputModel_: function(out, obj) { out('model_:"') if ( obj.model_.package ) out(obj.model_.package, '.') out(obj.model_.name, '"'); }, outputMap_: function(out, obj) { var str = ''; var first = true; out('{'); for ( var key in obj ) { var val = obj[key]; if ( ! first ) out(','); out(this.keyify(key), ': '); this.output(out, val); first = false; } out('}'); }, outputArray_: function(out, a, opt_defaultModel) { if ( a.length == 0 ) { out('[]'); return out; } var str = ''; var first = true; out('['); for ( var i = 0 ; i < a.length ; i++, first = false ) { var obj = a[i]; if ( ! first ) out(','); this.output(out, obj, opt_defaultModel); } out(']'); }, outputFunction_: function(out, obj) { out(obj); } }, pretty: { __proto__: AbstractFormatter, output: function(out, obj, opt_defaultModel, opt_indent) { var indent = opt_indent || ''; if ( Array.isArray(obj) ) { this.outputArray_(out, obj, null, indent); } else if ( typeof obj == 'string' ) { out('"'); out(JSONUtil.escape(obj)); out('"'); } else if ( obj instanceof Function ) { this.outputFunction_(out, obj, indent); } else if ( obj instanceof Date ) { out(obj.getTime()); } else if ( obj instanceof RegExp ) { out(obj.toString()); } else if ( obj instanceof Object ) { if ( obj.model_ ) this.outputObject_(out, obj, opt_defaultModel, indent); else this.outputMap_(out, obj, indent); } else if ( typeof obj === 'number' ) { if ( ! isFinite(obj) ) obj = null; out(obj); } else { if ( obj === undefined ) obj = null; out(obj); } }, outputObject_: function(out, obj, opt_defaultModel, opt_indent) { var indent = opt_indent || ''; var nestedIndent = indent + ' '; var str = ''; var first = true; out(/*"\n", */indent, '{\n'); if ( obj.model_.id && obj.model_.id !== opt_defaultModel ) { this.outputModel_(out, obj, nestedIndent); first = false; } var properties = obj.model_.getRuntimeProperties(); for ( var key in properties ) { var prop = properties[key]; if ( ! this.p(prop, obj) ) continue; if ( prop.name === 'parent' ) continue; if ( prop.name in obj.instance_ ) { var val = obj[prop.name]; if ( Array.isArray(val) && ! val.length ) continue; if ( ! first ) out(',\n'); out(nestedIndent, this.keyify(prop.name), ': '); if ( Array.isArray(val) && prop.subType ) { this.outputArray_(out, val, prop.subType, nestedIndent); } else { this.output(out, val, null, nestedIndent); } first = false; } } out('\n', indent, '}'); }, outputModel_: function(out, obj, indent) { out(indent, '"model_": "', obj.model_.id, '"'); }, outputMap_: function(out, obj, opt_indent) { var indent = opt_indent || ''; var nestedIndent = indent + ' '; var str = ''; var first = true; out(/*"\n",*/ indent, '{\n', nestedIndent); for ( var key in obj ) { var val = obj[key]; if ( ! first ) out(',\n'); out(nestedIndent, this.keyify(key), ': '); this.output(out, val, null, nestedIndent); first = false; } out('\n', indent, '}'); }, outputArray_: function(out, a, opt_defaultModel, opt_indent) { if ( a.length == 0 ) { out('[]'); return out; } var indent = opt_indent || ''; var nestedIndent = indent + ' '; var str = ''; var first = true; out('[\n'); for ( var i = 0 ; i < a.length ; i++, first = false ) { var obj = a[i]; if ( ! first ) out(',\n'); this.output(out, obj, opt_defaultModel, nestedIndent); } out('\n', indent, ']'); }, outputFunction_: function(out, obj, indent) { var str = obj.toString(); var lines = str.split('\n'); if ( lines.length == 1 ) { out(str); return; } var minIndent = 10000; for ( var i = 0 ; i < lines.length ; i++ ) { var j = 0; for ( ; j < lines[i].length && lines[i].charAt(j) === ' ' && j < minIndent ; j++ ); if ( j > 0 && j < minIndent ) minIndent = j; } if ( minIndent === 10000 ) { out(str); return; } for ( var i = 0 ; i < lines.length ; i++ ) { if ( lines[i].length && lines[i].charAt(0) === ' ' ) { lines[i] = indent + lines[i].substring(minIndent); } out(lines[i]); if ( i < lines.length-1 ) out('\n'); } } }, moreCompact: { __proto__: AbstractFormatter, // TODO: use short-names }, compressed: { __proto__: AbstractFormatter, stringify: function(obj) { return Iuppiter.Base64.encode(Iuppiter.compress(JSONUtil.compact.stringify(obj),true)); } } }; JSONUtil.prettyModel = { __proto__: JSONUtil.pretty, outputModel_: function(out, obj, indent) { out(indent, 'model_: "', obj.model_.id, '"'); }, keys_: {}, keyify: function(str) { if ( ! this.keys_.hasOwnProperty(str) ) { this.keys_[str] = /^[a-zA-Z\$_][0-9a-zA-Z$_]*$/.test(str) ? str : '"' + str + '"'; } return this.keys_[str]; } }; JSONUtil.stringify = JSONUtil.pretty.stringify.bind(JSONUtil.pretty); JSONUtil.stringifyObject = JSONUtil.pretty.stringifyObject.bind(JSONUtil.pretty); JSONUtil.output = JSONUtil.pretty.output.bind(JSONUtil.pretty); JSONUtil.where = JSONUtil.pretty.where.bind(JSONUtil.pretty); var NOT_TRANSIENT = function(prop) { return ! prop.transient; };