source: Dev/branches/rest-dojo-ui/client/dojox/json/ref.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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