source: Dev/trunk/src/client/dojox/json/ref.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 14.1 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/json",
4        "dojo/_base/kernel",
5        "dojo/_base/lang",
6        "dojo/date/stamp",
7        "dojox"
8],
9function(array, djson, dojo, lang, stamp, dojox){
10
11lang.getObject("json", true, dojox);
12
13return 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});
Note: See TracBrowser for help on using the repository browser.