
com.smartclient.debug.public.sc.client.language.Serialize.js Maven / Gradle / Ivy
Show all versions of smartgwt Show documentation
/*
* Isomorphic SmartClient
* Version SC_SNAPSHOT-2011-08-08 (2011-08-08)
* Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
* "SmartClient" is a trademark of Isomorphic Software, Inc.
*
* [email protected]
*
* http://smartclient.com/license
*/
//
// Comm.serialize() methods for the comm package
//
// XXX this package must not be dependant on the Comm package, because serialization is a useful
// feature completely apart from Comm. Unfortunately, the methods are currently expected to be on
// the Comm class, so if the Comm class doesn't exist we need to define it.
if (!isc.Comm) isc.ClassFactory.defineClass("Comm");
isc.Comm.addClassProperties( {
//> @classAttr Comm._simpleIdentifierRE (regex : /^[\$_a-zA-Z][\$\w]*$/ : IR)
// Regular expression to match legal identifier names so we can
// avoid unnecessary quotes when serializing.
// @group serialization
//<
_simpleIdentifierRE : /^[\$_a-zA-Z][\$\w]*$/,
//> @classAttr Comm.BACKREF_PREFIX (string : "$$BACKREF$$:" : IR)
// Prefix for back-references in serialized object references.
// @group serialization
//<
BACKREF_PREFIX : "$$BACKREF$$:",
indent : " "
});
isc.Comm.addClassMethods({
//> @classMethod Comm.serialize()
// Serialize an object of any type into a string, in a form that
// can be simply eval'd to rebuild the original object.
//
// @group serialization
//
// @param object (any) object to be serialized
// @param [indent] (boolean) should output be formatted with line breaks and indenting
// for readability? If unspecified, indenting occurs if
// +link{JSONEncoder.prettyPrint} is true.
// @return (string) serialized form of the object
//<
serialize : function (object, indent) {
var props = { strictQuoting:false, dateFormat:"dateConstructor"};
// if indent was explicitly specified, respect it
if (indent != null) props.prettyPrint = indent;
return isc.JSON.encode(object, props);
}
}); // END isc.addMethods(isc.Comm, {})
//> @class JSON
// Utilities for working with JSON data.
//
// @treeLocation Client Reference/Data Binding
// @visibility external
//<
isc.ClassFactory.defineClass("JSON", null, null, true);
isc.JSON.addClassProperties({
//> @classMethod JSON.encode()
// Serialize an object as a JSON string.
//
// Automatically handles circular references - see +link{JSONEncoder.circularReferenceMode}.
//
// @param object (any) object to serialize
// @param [settings] (JSONEncoder Properties) optional settings for encoding
// @return (String) object encoded as a JSON String
// @visibility external
//<
encode : function (object, settings) {
return isc.JSONEncoder.create(settings).encode(object);
},
//> @classMethod JSON.decode()
// De-serialize an object from JSON. Currently, this is simply a JavaScript eval() and should
// be used for trusted data only.
//
// @param jsonString (String) JSON data to be de-serialized
// @return (String) object derived from JSON String
// @visibility external
//<
decode : function (jsonString) {
//!OBFUSCATEOK
// Add parens to the JSON to avoid
// an issue where eval() gets confused and believes it is dealing with a block
return eval("(" + jsonString + ")");
}
});
//> @class JSONEncoder
// Class for encoding objects as JSON strings.
// @treeLocation Client Reference/Data Binding
// @visibility external
//<
isc.ClassFactory.defineClass("JSONEncoder");
isc.JSONEncoder.addClassProperties({
//> @classMethod JSONEncoder._serialize_remember() (A)
// Remember an object that has just been serialized it, so we don't
// attempt to serialize it again (and thus get into an endless loop).
// @group serialization
//
// @param objRefs (object[]) array of objects that have been serialized already so
// @param object (any) object o serialize
// @param path (string) global variable path to this object, for serializing object references
//<
// helper function to remember that we've already output a particular object in this serialize pass
_serialize_remember : function (objRefs, object, path) {
objRefs.obj.add(object);
objRefs.path.add(path);
},
// If this object is a Tree node, automatically clean off properties that the Tree adds to the
// node that should not be saved.
_serialize_cleanNode : function (object) {
var treeId = object["_isc_tree"];
if (treeId != null) {
var theTree = window[treeId];
if (theTree && theTree.parentProperty && object[theTree.parentProperty]) {
object = theTree.getCleanNodeData(object);
}
}
return object;
},
// Have we already output a particular object in this serialize pass? If so, return the path to
// that object.
_serialize_alreadyReferenced : function (objRefs, object) {
var rowNum = objRefs.obj.indexOf(object);
if (rowNum == -1) return null;
return objRefs.path[rowNum];
},
// Add a new identifier to an object path, used to build the path passed in to _serialize() above.
_serialize_addToPath : function (objPath, newIdentifier) {
if (isc.isA.Number(newIdentifier)) {
return objPath + "[" + newIdentifier + "]";
} else if (! isc.Comm._simpleIdentifierRE.test(newIdentifier)) {
return objPath + '["' + newIdentifier + '"]';
} else {
return objPath + "." + newIdentifier;
}
}
});
isc.JSONEncoder.addProperties({
//> @method JSONEncoder.encode()
// Serialize an object as a JSON string.
//
// @param object (any) object to serialize
// @return (String) object encoded as a JSON String
// @visibility external
//<
encode : function (object) {
this.objRefs = {obj:[],path:[]};
var retVal = this._serialize(object, this.prettyPrint ? "" : null , null);
this.objRefs = null;
return retVal
},
//> @type JSONDateFormat
// Format for encoding dates in JSON. Note you can override +link{JSONEncoder.encodeDate()}
// for a custom format.
//
// @value "xmlSchema" dates are is encoded as a String in XML Schema date format in UTC,
// for example, "2005-08-02" for logical date fields or "2005-08-01T21:35:48.350"
// for datetime
fields. See +link{group:dateFormatAndStorage,Date format and
// storage} for more information.
// @value "dateConstructor" dates are encoded as raw JavaScript code for creating a Date object,
// that is:
//
// new Date(1238792738633)
//
// This is not strictly valid JSON, but if eval()d, will result in an identical date object,
// regardless of timezone.
//
// @visibility external
//<
//> @type JSONInstanceSerializationMode
// Controls the output of the JSONEncoder when instances of SmartClient classes (eg a ListGrid)
// are included in the data to be serialized.
//
// @value "long" instances will be shown as a specially formatted JSON listing the most
// relevant properties of the instance. Result is not expected to
// decode()/eval() successfully if instances are included.
// @value "short" instances will be shown in a shorter format via a call to +link{isc.echoLeaf()}.
// Result is not expected to decode()/eval() successfully if instances are
// included.
// @value "skip" no output will be shown for instances (as though they were not present in the
// data). Result should decode()/eval() successfully (depending on other
// settings)
//
// @visibility external
//<
//> @attr JSONEncoder.serializeInstances (JSONInstanceSerializationMode : "long" : IR)
// Controls the output of the JSONEncoder when instances of SmartClient classes (eg a ListGrid)
// are included in the data to be serialized. See +link{JSONInstanceSerializationMode}.
//
// Note that the JSONEncoder does not support a format that will recreate the instance if
// passed to decode() or eval().
//
// @visibility external
//<
serializeInstances: true,
//> @attr JSONEncoder.skipInternalProperties (Boolean : false : IR)
// If true, don't show isc internal properties when encoding and object.
// @visibility external
//<
//> @attr JSONEncoder.showDebugOutput (boolean : false : IR)
// If objects that cannot be serialized to JSON are encountered during serialization, show a
// placeholder rather than just omitting them.
//
// The resulting String will not be valid JSON and so cannot be decoded/eval()'d
// @visibility external
//<
//> @attr JSONEncoder.dateFormat (JSONDateFormat : "xmlSchema" : IR)
// Format for encoding JavaScript Date values in JSON. See +link{type:JSONDateFormat} for
// valid options, or override +link{JSONEncoder.encodeDate()} to do something custom.
// @visibility external
//<
dateFormat: "xmlSchema",
//> @method JSONEncoder.encodeDate()
// Encode a JavaScript Date value.
//
// By default, follows the +link{JSONEncoder.dateFormat} setting. Override to do custom
// encoding.
//
// @param theDate (Date) JavaScript date object to be serialized
// @return (String) value to be included in result. If this value is intended to appear
// as a String it should include quotes (")
//
// @visibility external
//<
encodeDate : function (date) {
if (this.dateFormat == "dateConstructor") {
return date._serialize();
} else { // quotes for xml schema
return '"' + date.toSchemaDate() + '"';
}
},
//> @attr JSONEncoder.strictQuoting (boolean : true : IR)
// Whether all property names should be quoted, or only those property names that are not valid
// identifiers or are JavaScript reserved words (such as "true").
//
// Encoding only where required produces slightly shorter, more readable output which is still
// compatible with JavaScript's eval():
//
// {
// someProp : "someValue",
// "true" : "otherValue",
// otherProp : "otherValue"
// }
//
// .. but is not understood by many server-side JSON parser implementations.
// @visibility external
//<
strictQuoting: true,
//> @type JSONCircularReferenceMode
// What the +link{JSONEncoder} should do when it encounters a circular reference in an object
// structure.
// @value "omit" circular references in Arrays will be represented as a null entry, and objects
// will have a property with a null value
// @value "marker" leave a string marker, the +link{jsonEncoder.circularReferenceMarker},
// wherever a circular reference is found
// @value "path" leave a string marker followed by the path to the first occurrence of
// the circular reference from the top of the object tree that was serialized.
// This potentially allows the original object graph to be reconstructed.
// @visibility external
//<
//> @attr JSONEncoder.circularReferenceMode (JSONCircularReferenceMode : "path" : IR)
// What the JSONEncoder should do if it encounters a circular reference.
//
// @visibility external
//<
circularReferenceMode: "path",
//> @attr JSONEncoder.circularReferenceMarker (String : "$$BACKREF$$" : IR)
// The string marker used to represent circular references. See +link{circularReferenceMode}.
//
// @visibility external
//<
circularReferenceMarker: "$$BACKREF$$",
//> @attr JSONEncoder.prettyPrint (boolean : true : IR)
// Whether to add indentation to the returned JSON string. This makes the returned JSON much
// easier to read but adds size. Note that when delivering JSON responses compressed, the size
// difference between prettyPrinted JSON and normal JSON is negligible.
// @visibility external
//<
prettyPrint: true,
//> @method JSONEncoder._serialize() (A)
// Internal routine that actually does the serialization.
// @group serialization
//
// @param object (any) object to serialize
// @param prefix (string) string to put before each line of serialization output
// @param context (object) context that tracks previously encountered objects and
// settings
//
// @return (string) serialized object as a string
//<
_serialize : function (object, prefix, objPath) {
//if (this.autoDupMethods) this.duplicateMethod("_serialize");
if (!objPath) {
if (object && object.getID) objPath = object.getID();
else objPath = "";
}
if (object == null) return null;
// handle simple types
// In Safari a cross-frame scripting bug means that the 'asSource' method may not always be
// available as an instance method.
// call the static version of the same method if this happens.
if (isc.isA.String(object)) return (object.asSource != null ? object.asSource()
: String.asSource(object));
if (isc.isA.Function(object)) return null;
if (isc.isA.Number(object) || isc.isA.SpecialNumber(object)) return object;
if (isc.isA.Boolean(object)) return object;
if (isc.isA.Date(object)) return this.encodeDate(object);
// handle instances
if (isc.isAn.Instance(object)) {
if (this.serializeInstances == "skip") return null;
else if (this.serializeInstances == "short") return isc.echoLeaf(object);
// else "long".. fall through to logic below to have properties output
}
// handle classes in the same way
if (isc.isA.Class(object)) {
if (this.serializeInstances == "skip") return null;
else if (this.serializeInstances == "short") return isc.echoLeaf(object);
// else "long".. fall through to logic below to have properties output
}
// for complex types:
// detect infinite loops by checking if we've seen this object before.
// To disambiguate between true loops vs the same leaf object being encountered twice
// (such as a simple Array containing two Strings which appears in two spots). Only
// consider this a loop if the preceding occurrence of the object was some parent of
// ours.
var prevPath = isc.JSONEncoder._serialize_alreadyReferenced(this.objRefs, object);
if (prevPath != null && objPath.contains(prevPath)) {
// Note: check that the first char after "prevPath" is a path separator char in order
// to avoid false loop detection with "prop" and "prop2" having the same non-looping
// object (since "prop2" contains "prop").
var nextChar = objPath.substring(prevPath.length, prevPath.length+1);
//this.logWarn("backref: prevPath: " + prevPath + ", current: " + context.objPath +
// ", char after prevPath: " + nextChar);
if (nextChar == "." || nextChar == "[" || nextChar == "]") {
var mode = this.circularReferenceMode;
if (mode == "marker") {
return "'" + this.circularReferenceMarker + "'";
} else if (mode == "path") {
return "'" + this.circularReferenceMarker + ":" + prevPath + "'";
} else {
return null;
}
}
}
if (object == window) {
this.logWarn("Serializer encountered the window object at path: " + objPath
+" - returning null for this slot.");
return null;
}
// add the object to the list of objRefs so we can avoid an endless loop
isc.JSONEncoder._serialize_remember(this.objRefs, object, objPath);
// if there is a serialize method associated with this object, call that
if (isc.isA.Function(object._serialize)) return object._serialize(prefix, this.objRefs, objPath);
// handle arrays as a special case
if (isc.isAn.Array(object)) return this._serializeArray(object, objPath, this.objRefs, prefix);
var data;
// if the object has a getSerializeableFields, use whatever it returns, otherwise just use the object
if (object.getSerializeableFields) {
data = object.getSerializeableFields([], []);
} else {
data = object;
}
// and return anything else as a simple object
return this._serializeObject(data, objPath, this.objRefs, prefix);
},
//> @method JSONEncoder._serializeArray() (A)
// Internal routine to serialize an array.
// @group serialization
//
// @param object (any) object o serialize
// @param objPath (string) global variable path to this object, for serializing object references
// @param objRefs (object[]) array of objects that have been serialized already so
// we don't get into endless loops
// @param prefix (string) string to put before each line of serialization output
//
// @return (string) serialized object as a string
//<
_serializeArray : function (object, objPath, objRefs, prefix) {
// add the start array marker
var output = isc.SB.create();
output.append("[");
// for each element in the array
for (var i = 0, len = object.length; i < len; i++) {
var value = object[i];
// output that element
if (prefix != null) output.append("\r", prefix, isc.Comm.indent);
var objPath = isc.JSONEncoder._serialize_addToPath(objPath, i);
var serializedValue =
this._serialize(value,
(prefix != null ? prefix + isc.Comm.indent : null),
objPath);
// NOTE: need to concat serializedValue to have null/undef properly handled, normally
// skipped by StringBuffers
output.append(serializedValue + ",");
if (prefix != null) output.append(" ");
}
// get rid of the trailing comma, if any
output = output.toString();
var commaChar = output.lastIndexOf(",");
if (commaChar > -1) output = output.substring(0, commaChar);
// add the end array marker
if (prefix != null) output += "\r" + prefix;
output += "]";
// and return the output
return output;
},
//> @method JSONEncoder._serializeObject() (A)
// Internal routine to serialize an object.
// @group serialization
//
// @param object (any) object o serialize
// @param prefix (string) string to put before each line of serialization output
// @param objRefs (object[]) array of objects that have been serialized already so
// we don't get into endless loops
// @param objPath (string) global variable path to this object, for serializing object references
//
// @return (string) serialized object as a string
//<
_serializeObject : function (object, objPath, objRefs, prefix) {
// add the start object marker
var output = isc.SB.create(),
undefined;
object = isc.JSONEncoder._serialize_cleanNode(object);
try {
for (var key in object) break;
} catch (e) {
if (this.showDebugOutput) {
if (isc.isAn.XMLNode(object)) return isc.echoLeaf(object);
var message;
if (e.message) {
message = (e.message.asSource != null ? e.message.asSource()
: String.asSource(e.message));
return "{ cantEchoObject: " + message + "}";
} else {
return "{ cantEchoObject: 'unspecified error' }";
}
} else return null;
}
output.append("{");
// for each key in the object
for (var key in object) {
// skip null keys
if (key == null) continue;
// skip internal properties, if the flag is set
if (this.skipInternalProperties && (isc.startsWith(key, isc._underscore) || isc.startsWith(key, isc._dollar))) continue;
var value = object[key];
// if the value is a function, skip it
if (isc.isA.Function(value)) continue;
// omit instances entirely if so configured
if (key != isc.gwtRef && isc.isAn.Instance(value) && this.serializeInstances == "skip") continue;
// otherwise return the key:value pair
// convert the key to a string
var keyStr = key.toString();
// and if it isn't a simple identifier, quote it
if (!isc.Comm._simpleIdentifierRE.test(keyStr) || this.strictQuoting) {
if (keyStr.contains('"')) {
if (keyStr.contains("'")) {
keyStr = '"' + this.convertToEncodedQuotes(keyStr) + '"';
} else {
keyStr = "'" + keyStr + "'";
}
} else {
keyStr = '"' + keyStr + '"';
}
}
var objPath = isc.JSONEncoder._serialize_addToPath(objPath, key);
var serializedValue;
// don't try to serialize references to GWT Java objects
if (key == isc.gwtRef) {
if (!this.showDebugOutput) continue;
// show a marker if asked for debug output
serializedValue = "{GWT Java Obj}";
} else {
serializedValue =
this._serialize(value,
(prefix != null ? prefix + isc.Comm.indent : null),
objPath);
}
// skip values that resolve to undefined
//if (serializedValue === undefined) {
// continue;
//}
// now output the key : value pair
if (prefix != null) output.append("\r", prefix, isc.Comm.indent);
// NOTE: need to concat serializedValue to have null/undef properly handled, normally
// skipped by StringBuffers
output.append(keyStr, ":" + serializedValue, ",");
if (prefix != null) output.append(" ");
}
// get rid of the trailing comma, if any
output = output.toString();
var commaChar = output.lastIndexOf(",");
if (commaChar > -1) output = output.substring(0, commaChar);
// add the end object marker
if (prefix != null) output += "\r" + prefix;
output += "}";
// and return the output
return output;
},
// Converts a string so that embedded ' and " characters are converted to the HTML encodings
// ' and " Only used if a key string contains both ' and " (otherwise, we just
// quote it using the symbol that isn't contained in the key name)
convertToEncodedQuotes : function (string) {
return string.replace(String._doubleQuoteRegex, """).
replace(String._singleQuoteRegex, "'");
},
convertFromEncodedQuotes : function (string) {
return string.replace(new RegExp(""", "g"), '"').
replace(new RegExp("'", "g"), "'");
}
});