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