ref.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.json.ref"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.json.ref"] = true;
  8. dojo.provide("dojox.json.ref");
  9. dojo.require("dojo.date.stamp");
  10. dojox.json.ref = {
  11. // summary:
  12. // Adds advanced JSON {de}serialization capabilities to the base json library.
  13. // This enhances the capabilities of dojo.toJson and dojo.fromJson,
  14. // adding referencing support, date handling, and other extra format handling.
  15. // On parsing, references are resolved. When references are made to
  16. // ids/objects that have been loaded yet, the loader function will be set to
  17. // _loadObject to denote a lazy loading (not loaded yet) object.
  18. resolveJson: function(/*Object*/ root,/*Object?*/ args){
  19. // summary:
  20. // Indexes and resolves references in the JSON object.
  21. // description:
  22. // A JSON Schema object that can be used to advise the handling of the JSON (defining ids, date properties, urls, etc)
  23. //
  24. // root:
  25. // The root object of the object graph to be processed
  26. // args:
  27. // Object with additional arguments:
  28. //
  29. // The *index* parameter.
  30. // This is the index object (map) to use to store an index of all the objects.
  31. // If you are using inter-message referencing, you must provide the same object for each call.
  32. // The *defaultId* parameter.
  33. // This is the default id to use for the root object (if it doesn't define it's own id)
  34. // The *idPrefix* parameter.
  35. // This the prefix to use for the ids as they enter the index. This allows multiple tables
  36. // to use ids (that might otherwise collide) that enter the same global index.
  37. // idPrefix should be in the form "/Service/". For example,
  38. // if the idPrefix is "/Table/", and object is encountered {id:"4",...}, this would go in the
  39. // index as "/Table/4".
  40. // The *idAttribute* parameter.
  41. // This indicates what property is the identity property. This defaults to "id"
  42. // The *assignAbsoluteIds* parameter.
  43. // This indicates that the resolveJson should assign absolute ids (__id) as the objects are being parsed.
  44. //
  45. // The *schemas* parameter
  46. // This provides a map of schemas, from which prototypes can be retrieved
  47. // The *loader* parameter
  48. // This is a function that is called added to the reference objects that can't be resolved (lazy objects)
  49. // return:
  50. // An object, the result of the processing
  51. args = args || {};
  52. var idAttribute = args.idAttribute || 'id';
  53. var refAttribute = this.refAttribute;
  54. var idAsRef = args.idAsRef;
  55. var prefix = args.idPrefix || '';
  56. var assignAbsoluteIds = args.assignAbsoluteIds;
  57. var index = args.index || {}; // create an index if one doesn't exist
  58. var timeStamps = args.timeStamps;
  59. var ref,reWalk=[];
  60. var pathResolveRegex = /^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/;
  61. var addProp = this._addProp;
  62. var F = function(){};
  63. function walk(it, stop, defaultId, needsPrefix, schema, defaultObject){
  64. // this walks the new graph, resolving references and making other changes
  65. var i, update, val, id = idAttribute in it ? it[idAttribute] : defaultId;
  66. if(idAttribute in it || ((id !== undefined) && needsPrefix)){
  67. id = (prefix + id).replace(pathResolveRegex,'$2$3');
  68. }
  69. var target = defaultObject || it;
  70. if(id !== undefined){ // if there is an id available...
  71. if(assignAbsoluteIds){
  72. it.__id = id;
  73. }
  74. if(args.schemas && (!(it instanceof Array)) && // won't try on arrays to do prototypes, plus it messes with queries
  75. (val = id.match(/^(.+\/)[^\.\[]*$/))){ // if it has a direct table id (no paths)
  76. schema = args.schemas[val[1]];
  77. }
  78. // if the id already exists in the system, we should use the existing object, and just
  79. // update it... as long as the object is compatible
  80. if(index[id] && ((it instanceof Array) == (index[id] instanceof Array))){
  81. target = index[id];
  82. delete target.$ref; // remove this artifact
  83. delete target._loadObject;
  84. update = true;
  85. }else{
  86. var proto = schema && schema.prototype; // and if has a prototype
  87. if(proto){
  88. // if the schema defines a prototype, that needs to be the prototype of the object
  89. F.prototype = proto;
  90. target = new F();
  91. }
  92. }
  93. index[id] = target; // add the prefix, set _id, and index it
  94. if(timeStamps){
  95. timeStamps[id] = args.time;
  96. }
  97. }
  98. while(schema){
  99. var properties = schema.properties;
  100. if(properties){
  101. for(i in it){
  102. var propertyDefinition = properties[i];
  103. if(propertyDefinition && propertyDefinition.format == 'date-time' && typeof it[i] == 'string'){
  104. it[i] = dojo.date.stamp.fromISOString(it[i]);
  105. }
  106. }
  107. }
  108. schema = schema["extends"];
  109. }
  110. var length = it.length;
  111. for(i in it){
  112. if(i==length){
  113. break;
  114. }
  115. if(it.hasOwnProperty(i)){
  116. val=it[i];
  117. if((typeof val =='object') && val && !(val instanceof Date) && i != '__parent'){
  118. ref=val[refAttribute] || (idAsRef && val[idAttribute]);
  119. if(!ref || !val.__parent){
  120. if(it != reWalk){
  121. val.__parent = target;
  122. }
  123. }
  124. if(ref){ // a reference was found
  125. // make sure it is a safe reference
  126. delete it[i];// remove the property so it doesn't resolve to itself in the case of id.propertyName lazy values
  127. var path = ref.toString().replace(/(#)([^\.\[])/,'$1.$2').match(/(^([^\[]*\/)?[^#\.\[]*)#?([\.\[].*)?/); // divide along the path
  128. if(index[(prefix + ref).replace(pathResolveRegex,'$2$3')]){
  129. ref = index[(prefix + ref).replace(pathResolveRegex,'$2$3')];
  130. }else if((ref = (path[1]=='$' || path[1]=='this' || path[1]=='') ? root : index[(prefix + path[1]).replace(pathResolveRegex,'$2$3')])){ // a $ indicates to start with the root, otherwise start with an id
  131. // if there is a path, we will iterate through the path references
  132. if(path[3]){
  133. path[3].replace(/(\[([^\]]+)\])|(\.?([^\.\[]+))/g,function(t,a,b,c,d){
  134. ref = ref && ref[b ? b.replace(/[\"\'\\]/,'') : d];
  135. });
  136. }
  137. }
  138. if(ref){
  139. val = ref;
  140. }else{
  141. // otherwise, no starting point was found (id not found), if stop is set, it does not exist, we have
  142. // unloaded reference, if stop is not set, it may be in a part of the graph not walked yet,
  143. // we will wait for the second loop
  144. if(!stop){
  145. var rewalking;
  146. if(!rewalking){
  147. reWalk.push(target); // we need to rewalk it to resolve references
  148. }
  149. rewalking = true; // we only want to add it once
  150. val = walk(val, false, val[refAttribute], true, propertyDefinition);
  151. // create a lazy loaded object
  152. val._loadObject = args.loader;
  153. }
  154. }
  155. }else{
  156. if(!stop){ // if we are in stop, that means we are in the second loop, and we only need to check this current one,
  157. // further walking may lead down circular loops
  158. val = walk(
  159. val,
  160. reWalk==it,
  161. id === undefined ? undefined : addProp(id, i), // the default id to use
  162. false,
  163. propertyDefinition,
  164. // if we have an existing object child, we want to
  165. // maintain it's identity, so we pass it as the default object
  166. target != it && typeof target[i] == 'object' && target[i]
  167. );
  168. }
  169. }
  170. }
  171. it[i] = val;
  172. if(target!=it && !target.__isDirty){// do updates if we are updating an existing object and it's not dirty
  173. var old = target[i];
  174. target[i] = val; // only update if it changed
  175. if(update && val !== old && // see if it is different
  176. !target._loadObject && // no updates if we are just lazy loading
  177. !(i.charAt(0) == '_' && i.charAt(1) == '_') && i != "$ref" &&
  178. !(val instanceof Date && old instanceof Date && val.getTime() == old.getTime()) && // make sure it isn't an identical date
  179. !(typeof val == 'function' && typeof old == 'function' && val.toString() == old.toString()) && // make sure it isn't an indentical function
  180. index.onUpdate){
  181. index.onUpdate(target,i,old,val); // call the listener for each update
  182. }
  183. }
  184. }
  185. }
  186. if(update && (idAttribute in it || target instanceof Array)){
  187. // this means we are updating with a full representation of the object, we need to remove deleted
  188. for(i in target){
  189. if(!target.__isDirty && target.hasOwnProperty(i) && !it.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && !(target instanceof Array && isNaN(i))){
  190. if(index.onUpdate && i != "_loadObject" && i != "_idAttr"){
  191. index.onUpdate(target,i,target[i],undefined); // call the listener for each update
  192. }
  193. delete target[i];
  194. while(target instanceof Array && target.length && target[target.length-1] === undefined){
  195. // shorten the target if necessary
  196. target.length--;
  197. }
  198. }
  199. }
  200. }else{
  201. if(index.onLoad){
  202. index.onLoad(target);
  203. }
  204. }
  205. return target;
  206. }
  207. if(root && typeof root == 'object'){
  208. root = walk(root,false,args.defaultId, true); // do the main walk through
  209. walk(reWalk,false); // re walk any parts that were not able to resolve references on the first round
  210. }
  211. return root;
  212. },
  213. fromJson: function(/*String*/ str,/*Object?*/ args){
  214. // summary:
  215. // evaluates the passed string-form of a JSON object.
  216. //
  217. // str:
  218. // a string literal of a JSON item, for instance:
  219. // '{ "foo": [ "bar", 1, { "baz": "thud" } ] }'
  220. // args: See resolveJson
  221. //
  222. // return:
  223. // An object, the result of the evaluation
  224. function ref(target){ // support call styles references as well
  225. var refObject = {};
  226. refObject[this.refAttribute] = target;
  227. return refObject;
  228. }
  229. try{
  230. var root = eval('(' + str + ')'); // do the eval
  231. }catch(e){
  232. throw new SyntaxError("Invalid JSON string: " + e.message + " parsing: "+ str);
  233. }
  234. if(root){
  235. return this.resolveJson(root, args);
  236. }
  237. return root;
  238. },
  239. toJson: function(/*Object*/ it, /*Boolean?*/ prettyPrint, /*Object?*/ idPrefix, /*Object?*/ indexSubObjects){
  240. // summary:
  241. // Create a JSON serialization of an object.
  242. // This has support for referencing, including circular references, duplicate references, and out-of-message references
  243. // id and path-based referencing is supported as well and is based on http://www.json.com/2007/10/19/json-referencing-proposal-and-library/.
  244. //
  245. // it:
  246. // an object to be serialized.
  247. //
  248. // prettyPrint:
  249. // if true, we indent objects and arrays to make the output prettier.
  250. // The variable dojo.toJsonIndentStr is used as the indent string
  251. // -- to use something other than the default (tab),
  252. // change that variable before calling dojo.toJson().
  253. //
  254. // idPrefix: The prefix that has been used for the absolute ids
  255. //
  256. // return:
  257. // a String representing the serialized version of the passed object.
  258. var useRefs = this._useRefs;
  259. var addProp = this._addProp;
  260. var refAttribute = this.refAttribute;
  261. idPrefix = idPrefix || ''; // the id prefix for this context
  262. var paths={};
  263. var generated = {};
  264. function serialize(it,path,_indentStr){
  265. if(typeof it == 'object' && it){
  266. var value;
  267. if(it instanceof Date){ // properly serialize dates
  268. return '"' + dojo.date.stamp.toISOString(it,{zulu:true}) + '"';
  269. }
  270. var id = it.__id;
  271. if(id){ // we found an identifiable object, we will just serialize a reference to it... unless it is the root
  272. if(path != '#' && ((useRefs && !id.match(/#/)) || paths[id])){
  273. var ref = id;
  274. if(id.charAt(0)!='#'){
  275. if(it.__clientId == id){
  276. ref = "cid:" + id;
  277. }else if(id.substring(0, idPrefix.length) == idPrefix){ // see if the reference is in the current context
  278. // a reference with a prefix matching the current context, the prefix should be removed
  279. ref = id.substring(idPrefix.length);
  280. }else{
  281. // a reference to a different context, assume relative url based referencing
  282. ref = id;
  283. }
  284. }
  285. var refObject = {};
  286. refObject[refAttribute] = ref;
  287. return serialize(refObject,'#');
  288. }
  289. path = id;
  290. }else{
  291. it.__id = path; // we will create path ids for other objects in case they are circular
  292. generated[path] = it;
  293. }
  294. paths[path] = it;// save it here so they can be deleted at the end
  295. _indentStr = _indentStr || "";
  296. var nextIndent = prettyPrint ? _indentStr + dojo.toJsonIndentStr : "";
  297. var newLine = prettyPrint ? "\n" : "";
  298. var sep = prettyPrint ? " " : "";
  299. if(it instanceof Array){
  300. var res = dojo.map(it, function(obj,i){
  301. var val = serialize(obj, addProp(path, i), nextIndent);
  302. if(typeof val != "string"){
  303. val = "undefined";
  304. }
  305. return newLine + nextIndent + val;
  306. });
  307. return "[" + res.join("," + sep) + newLine + _indentStr + "]";
  308. }
  309. var output = [];
  310. for(var i in it){
  311. if(it.hasOwnProperty(i)){
  312. var keyStr;
  313. if(typeof i == "number"){
  314. keyStr = '"' + i + '"';
  315. }else if(typeof i == "string" && (i.charAt(0) != '_' || i.charAt(1) != '_')){
  316. // we don't serialize our internal properties __id and __clientId
  317. keyStr = dojo._escapeString(i);
  318. }else{
  319. // skip non-string or number keys
  320. continue;
  321. }
  322. var val = serialize(it[i],addProp(path, i),nextIndent);
  323. if(typeof val != "string"){
  324. // skip non-serializable values
  325. continue;
  326. }
  327. output.push(newLine + nextIndent + keyStr + ":" + sep + val);
  328. }
  329. }
  330. return "{" + output.join("," + sep) + newLine + _indentStr + "}";
  331. }else if(typeof it == "function" && dojox.json.ref.serializeFunctions){
  332. return it.toString();
  333. }
  334. return dojo.toJson(it); // use the default serializer for primitives
  335. }
  336. var json = serialize(it,'#','');
  337. if(!indexSubObjects){
  338. for(var i in generated) {// cleanup the temporary path-generated ids
  339. delete generated[i].__id;
  340. }
  341. }
  342. return json;
  343. },
  344. _addProp: function(id, prop){
  345. return id + (id.match(/#/) ? id.length == 1 ? '' : '.' : '#') + prop;
  346. },
  347. // refAttribute: String
  348. // This indicates what property is the reference property. This acts like the idAttribute
  349. // except that this is used to indicate the current object is a reference or only partially
  350. // loaded. This defaults to "$ref".
  351. refAttribute: "$ref",
  352. _useRefs: false,
  353. serializeFunctions: false
  354. }
  355. }