-
Notifications
You must be signed in to change notification settings - Fork 1
/
json.ref.js
413 lines (394 loc) · 18.1 KB
/
json.ref.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
// This plugin wraps dojox.json.ref so we don't need dojo, requires jQuery (for map function)
/*
The "New" BSD License:
**********************
Copyright (c) 2005-2010, The Dojo Foundation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Dojo Foundation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function(){
var dojo = {};
var dojox = {};
dojox.json = {};
dojo.toJsonIndentStr = "\t";
dojo._escapeString = function(/*String*/str){
//summary:
// Adds escape sequences for non-visual characters, double quote and
// backslash and surrounds with double quotes to form a valid string
// literal.
return ('"' + str.replace(/(["\\])/g, '\\$1') + '"').
replace(/[\f]/g, "\\f").replace(/[\b]/g, "\\b").replace(/[\n]/g, "\\n").
replace(/[\t]/g, "\\t").replace(/[\r]/g, "\\r"); // string
}
dojo.toJson = function(obj) {
return JSON.stringify(obj);
}
dojo.map = jQuery.map;
// Implement!
// dojo.date.stamp.toISOString;
// dojo.date.stamp.fromISOString;
dojox.json.ref = {
// summary:
// Adds advanced JSON {de}serialization capabilities to the base json library.
// This enhances the capabilities of dojo.toJson and dojo.fromJson,
// adding referencing support, date handling, and other extra format handling.
// On parsing, references are resolved. When references are made to
// ids/objects that have been loaded yet, the loader function will be set to
// _loadObject to denote a lazy loading (not loaded yet) object.
resolveJson: function(/*Object*/ root,/*Object?*/ args){
// summary:
// Indexes and resolves references in the JSON object.
// description:
// A JSON Schema object that can be used to advise the handling of the JSON (defining ids, date properties, urls, etc)
//
// root:
// The root object of the object graph to be processed
// args:
// Object with additional arguments:
//
// The *index* parameter.
// This is the index object (map) to use to store an index of all the objects.
// If you are using inter-message referencing, you must provide the same object for each call.
// The *defaultId* parameter.
// This is the default id to use for the root object (if it doesn't define it's own id)
// The *idPrefix* parameter.
// This the prefix to use for the ids as they enter the index. This allows multiple tables
// to use ids (that might otherwise collide) that enter the same global index.
// idPrefix should be in the form "/Service/". For example,
// if the idPrefix is "/Table/", and object is encountered {id:"4",...}, this would go in the
// index as "/Table/4".
// The *idAttribute* parameter.
// This indicates what property is the identity property. This defaults to "id"
// The *assignAbsoluteIds* parameter.
// This indicates that the resolveJson should assign absolute ids (__id) as the objects are being parsed.
//
// The *schemas* parameter
// This provides a map of schemas, from which prototypes can be retrieved
// The *loader* parameter
// This is a function that is called added to the reference objects that can't be resolved (lazy objects)
// return:
// An object, the result of the processing
args = args || {};
var idAttribute = args.idAttribute || 'id';
var refAttribute = this.refAttribute;
var idAsRef = args.idAsRef;
var prefix = args.idPrefix || '';
var assignAbsoluteIds = args.assignAbsoluteIds;
var index = args.index || {}; // create an index if one doesn't exist
var timeStamps = args.timeStamps;
var ref,reWalk=[];
var pathResolveRegex = /^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/;
var addProp = this._addProp;
var F = function(){};
function walk(it, stop, defaultId, needsPrefix, schema, defaultObject){
// this walks the new graph, resolving references and making other changes
var i, update, val, id = idAttribute in it ? it[idAttribute] : defaultId;
if(idAttribute in it || ((id !== undefined) && needsPrefix)){
id = (prefix + id).replace(pathResolveRegex,'$2$3');
}
var target = defaultObject || it;
if(id !== undefined){ // if there is an id available...
if(assignAbsoluteIds){
it.__id = id;
}
if(args.schemas && (!(it instanceof Array)) && // won't try on arrays to do prototypes, plus it messes with queries
(val = id.match(/^(.+\/)[^\.\[]*$/))){ // if it has a direct table id (no paths)
schema = args.schemas[val[1]];
}
// if the id already exists in the system, we should use the existing object, and just
// update it... as long as the object is compatible
if(index[id] && ((it instanceof Array) == (index[id] instanceof Array))){
target = index[id];
delete target.$ref; // remove this artifact
delete target._loadObject;
update = true;
}else{
var proto = schema && schema.prototype; // and if has a prototype
if(proto){
// if the schema defines a prototype, that needs to be the prototype of the object
F.prototype = proto;
target = new F();
}
}
index[id] = target; // add the prefix, set _id, and index it
if(timeStamps){
timeStamps[id] = args.time;
}
}
while(schema){
var properties = schema.properties;
if(properties){
for(i in it){
var propertyDefinition = properties[i];
if(propertyDefinition && propertyDefinition.format == 'date-time' && typeof it[i] == 'string'){
it[i] = dojo.date.stamp.fromISOString(it[i]);
}
}
}
schema = schema["extends"];
}
var length = it.length;
for(i in it){
if(i==length){
break;
}
if(it.hasOwnProperty(i)){
val=it[i];
if((typeof val =='object') && val && !(val instanceof Date) && i != '__parent'){
ref=val[refAttribute] || (idAsRef && val[idAttribute]);
if(!ref || !val.__parent){
if(it != reWalk){
val.__parent = target;
}
}
if(ref){ // a reference was found
// make sure it is a safe reference
delete it[i];// remove the property so it doesn't resolve to itself in the case of id.propertyName lazy values
var path = ref.toString().replace(/(#)([^\.\[])/,'$1.$2').match(/(^([^\[]*\/)?[^#\.\[]*)#?([\.\[].*)?/); // divide along the path
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
// if there is a path, we will iterate through the path references
if(path[3]){
path[3].replace(/(\[([^\]]+)\])|(\.?([^\.\[]+))/g,function(t,a,b,c,d){
ref = ref && ref[b ? b.replace(/[\"\'\\]/,'') : d];
});
}
}
if(ref){
val = ref;
}else{
// otherwise, no starting point was found (id not found), if stop is set, it does not exist, we have
// unloaded reference, if stop is not set, it may be in a part of the graph not walked yet,
// we will wait for the second loop
if(!stop){
var rewalking;
if(!rewalking){
reWalk.push(target); // we need to rewalk it to resolve references
}
rewalking = true; // we only want to add it once
val = walk(val, false, val[refAttribute], true, propertyDefinition);
// create a lazy loaded object
val._loadObject = args.loader;
}
}
}else{
if(!stop){ // if we are in stop, that means we are in the second loop, and we only need to check this current one,
// further walking may lead down circular loops
val = walk(
val,
reWalk==it,
id === undefined ? undefined : addProp(id, i), // the default id to use
false,
propertyDefinition,
// if we have an existing object child, we want to
// maintain it's identity, so we pass it as the default object
target != it && typeof target[i] == 'object' && target[i]
);
}
}
}
it[i] = val;
if(target!=it && !target.__isDirty){// do updates if we are updating an existing object and it's not dirty
var old = target[i];
target[i] = val; // only update if it changed
if(update && val !== old && // see if it is different
!target._loadObject && // no updates if we are just lazy loading
!(i.charAt(0) == '_' && i.charAt(1) == '_') && i != "$ref" &&
!(val instanceof Date && old instanceof Date && val.getTime() == old.getTime()) && // make sure it isn't an identical date
!(typeof val == 'function' && typeof old == 'function' && val.toString() == old.toString()) && // make sure it isn't an indentical function
index.onUpdate){
index.onUpdate(target,i,old,val); // call the listener for each update
}
}
}
}
if(update && (idAttribute in it || target instanceof Array)){
// this means we are updating with a full representation of the object, we need to remove deleted
for(i in target){
if(!target.__isDirty && target.hasOwnProperty(i) && !it.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && !(target instanceof Array && isNaN(i))){
if(index.onUpdate && i != "_loadObject" && i != "_idAttr"){
index.onUpdate(target,i,target[i],undefined); // call the listener for each update
}
delete target[i];
while(target instanceof Array && target.length && target[target.length-1] === undefined){
// shorten the target if necessary
target.length--;
}
}
}
}else{
if(index.onLoad){
index.onLoad(target);
}
}
return target;
}
if(root && typeof root == 'object'){
root = walk(root,false,args.defaultId, true); // do the main walk through
walk(reWalk,false); // re walk any parts that were not able to resolve references on the first round
}
return root;
},
fromJson: function(/*String*/ str,/*Object?*/ args){
// summary:
// evaluates the passed string-form of a JSON object.
//
// str:
// a string literal of a JSON item, for instance:
// '{ "foo": [ "bar", 1, { "baz": "thud" } ] }'
// args: See resolveJson
//
// return:
// An object, the result of the evaluation
function ref(target){ // support call styles references as well
var refObject = {};
refObject[this.refAttribute] = target;
return refObject;
}
try{
var root = eval('(' + str + ')'); // do the eval
}catch(e){
throw new SyntaxError("Invalid JSON string: " + e.message + " parsing: "+ str);
}
if(root){
return this.resolveJson(root, args);
}
return root;
},
toJson: function(/*Object*/ it, /*Boolean?*/ prettyPrint, /*Object?*/ idPrefix, /*Object?*/ indexSubObjects){
// summary:
// Create a JSON serialization of an object.
// This has support for referencing, including circular references, duplicate references, and out-of-message references
// 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/.
//
// it:
// an object to be serialized.
//
// prettyPrint:
// if true, we indent objects and arrays to make the output prettier.
// The variable dojo.toJsonIndentStr is used as the indent string
// -- to use something other than the default (tab),
// change that variable before calling dojo.toJson().
//
// idPrefix: The prefix that has been used for the absolute ids
//
// return:
// a String representing the serialized version of the passed object.
var useRefs = this._useRefs;
var addProp = this._addProp;
var refAttribute = this.refAttribute;
idPrefix = idPrefix || ''; // the id prefix for this context
var paths={};
var generated = {};
function serialize(it,path,_indentStr){
if(typeof it == 'object' && it){
var value;
if(it instanceof Date){ // properly serialize dates
return '"' + dojo.date.stamp.toISOString(it,{zulu:true}) + '"';
}
var id = it.__id;
if(id){ // we found an identifiable object, we will just serialize a reference to it... unless it is the root
if(path != '#' && ((useRefs && !id.match(/#/)) || paths[id])){
var ref = id;
if(id.charAt(0)!='#'){
if(it.__clientId == id){
ref = "cid:" + id;
}else if(id.substring(0, idPrefix.length) == idPrefix){ // see if the reference is in the current context
// a reference with a prefix matching the current context, the prefix should be removed
ref = id.substring(idPrefix.length);
}else{
// a reference to a different context, assume relative url based referencing
ref = id;
}
}
var refObject = {};
refObject[refAttribute] = ref;
return serialize(refObject,'#');
}
path = id;
}else{
it.__id = path; // we will create path ids for other objects in case they are circular
generated[path] = it;
}
paths[path] = it;// save it here so they can be deleted at the end
_indentStr = _indentStr || "";
var nextIndent = prettyPrint ? _indentStr + dojo.toJsonIndentStr : "";
var newLine = prettyPrint ? "\n" : "";
var sep = prettyPrint ? " " : "";
if(it instanceof Array){
var res = dojo.map(it, function(obj,i){
var val = serialize(obj, addProp(path, i), nextIndent);
if(typeof val != "string"){
val = "undefined";
}
return newLine + nextIndent + val;
});
return "[" + res.join("," + sep) + newLine + _indentStr + "]";
}
var output = [];
for(var i in it){
if(it.hasOwnProperty(i)){
var keyStr;
if(typeof i == "number"){
keyStr = '"' + i + '"';
}else if(typeof i == "string" && (i.charAt(0) != '_' || i.charAt(1) != '_')){
// we don't serialize our internal properties __id and __clientId
keyStr = dojo._escapeString(i);
}else{
// skip non-string or number keys
continue;
}
var val = serialize(it[i],addProp(path, i),nextIndent);
if(typeof val != "string"){
// skip non-serializable values
continue;
}
output.push(newLine + nextIndent + keyStr + ":" + sep + val);
}
}
return "{" + output.join("," + sep) + newLine + _indentStr + "}";
}else if(typeof it == "function" && dojox.json.ref.serializeFunctions){
return it.toString();
}
return dojo.toJson(it); // use the default serializer for primitives
}
var json = serialize(it,'#','');
if(!indexSubObjects){
for(var i in generated) {// cleanup the temporary path-generated ids
delete generated[i].__id;
}
}
return json;
},
_addProp: function(id, prop){
return id + (id.match(/#/) ? id.length == 1 ? '' : '.' : '#') + prop;
},
// refAttribute: String
// This indicates what property is the reference property. This acts like the idAttribute
// except that this is used to indicate the current object is a reference or only partially
// loaded. This defaults to "$ref".
refAttribute: "$ref",
_useRefs: false,
serializeFunctions: false
}
jQuery.fromJsonRef = function(str, args) { return dojox.json.ref.fromJson(str, args); };
jQuery.toJsonRef = function(str, args) { return dojox.json.ref.toJson(str, args); };
})();