Skip to content

Commit 13b0092

Browse files
authored
Merge pull request #195 from paztis/master
Performances problems fixes on big files with a lot of references on same big object
2 parents d3bc198 + 991e1a0 commit 13b0092

File tree

1 file changed

+73
-36
lines changed

1 file changed

+73
-36
lines changed

lib/dereference.js

+73-36
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = dereference;
1616
*/
1717
function dereference (parser, options) {
1818
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
19-
let dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", [], parser.$refs, options);
19+
let dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", [], [], {}, parser.$refs, options);
2020
parser.$refs.circular = dereferenced.circular;
2121
parser.schema = dereferenced.value;
2222
}
@@ -28,60 +28,65 @@ function dereference (parser, options) {
2828
* @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
2929
* @param {string} pathFromRoot - The path of `obj` from the schema root
3030
* @param {object[]} parents - An array of the parent objects that have already been dereferenced
31+
* @param {object[]} processedObjects - An array of all the objects that have already been processed
32+
* @param {object} dereferencedCache - An map of all the dereferenced objects
3133
* @param {$Refs} $refs
3234
* @param {$RefParserOptions} options
3335
* @returns {{value: object, circular: boolean}}
3436
*/
35-
function crawl (obj, path, pathFromRoot, parents, $refs, options) {
37+
function crawl (obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) {
3638
let dereferenced;
3739
let result = {
3840
value: obj,
3941
circular: false
4042
};
4143

42-
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
43-
parents.push(obj);
44+
if (options.dereference.circular === "ignore" || processedObjects.indexOf(obj) === -1) {
45+
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
46+
parents.push(obj);
47+
processedObjects.push(obj);
4448

45-
if ($Ref.isAllowed$Ref(obj, options)) {
46-
dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, $refs, options);
47-
result.circular = dereferenced.circular;
48-
result.value = dereferenced.value;
49-
}
50-
else {
51-
for (let key of Object.keys(obj)) {
52-
let keyPath = Pointer.join(path, key);
53-
let keyPathFromRoot = Pointer.join(pathFromRoot, key);
54-
let value = obj[key];
55-
let circular = false;
56-
57-
if ($Ref.isAllowed$Ref(value, options)) {
58-
dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, $refs, options);
59-
circular = dereferenced.circular;
60-
// Avoid pointless mutations; breaks frozen objects to no profit
61-
if (obj[key] !== dereferenced.value) {
62-
obj[key] = dereferenced.value;
63-
}
64-
}
65-
else {
66-
if (parents.indexOf(value) === -1) {
67-
dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, $refs, options);
49+
if ($Ref.isAllowed$Ref(obj, options)) {
50+
dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
51+
result.circular = dereferenced.circular;
52+
result.value = dereferenced.value;
53+
}
54+
else {
55+
for (let key of Object.keys(obj)) {
56+
let keyPath = Pointer.join(path, key);
57+
let keyPathFromRoot = Pointer.join(pathFromRoot, key);
58+
let value = obj[key];
59+
let circular = false;
60+
61+
if ($Ref.isAllowed$Ref(value, options)) {
62+
dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
6863
circular = dereferenced.circular;
6964
// Avoid pointless mutations; breaks frozen objects to no profit
7065
if (obj[key] !== dereferenced.value) {
7166
obj[key] = dereferenced.value;
7267
}
7368
}
7469
else {
75-
circular = foundCircularReference(keyPath, $refs, options);
70+
if (parents.indexOf(value) === -1) {
71+
dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
72+
circular = dereferenced.circular;
73+
// Avoid pointless mutations; breaks frozen objects to no profit
74+
if (obj[key] !== dereferenced.value) {
75+
obj[key] = dereferenced.value;
76+
}
77+
}
78+
else {
79+
circular = foundCircularReference(keyPath, $refs, options);
80+
}
7681
}
77-
}
7882

79-
// Set the "isCircular" flag if this or any other property is circular
80-
result.circular = result.circular || circular;
83+
// Set the "isCircular" flag if this or any other property is circular
84+
result.circular = result.circular || circular;
85+
}
8186
}
82-
}
8387

84-
parents.pop();
88+
parents.pop();
89+
}
8590
}
8691

8792
return result;
@@ -94,14 +99,38 @@ function crawl (obj, path, pathFromRoot, parents, $refs, options) {
9499
* @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash
95100
* @param {string} pathFromRoot - The path of `$ref` from the schema root
96101
* @param {object[]} parents - An array of the parent objects that have already been dereferenced
102+
* @param {object[]} processedObjects - An array of all the objects that have already been dereferenced
103+
* @param {object} dereferencedCache - An map of all the dereferenced objects
97104
* @param {$Refs} $refs
98105
* @param {$RefParserOptions} options
99106
* @returns {{value: object, circular: boolean}}
100107
*/
101-
function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
108+
function dereference$Ref ($ref, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) {
102109
// console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path);
103110

104111
let $refPath = url.resolve(path, $ref.$ref);
112+
113+
if (dereferencedCache[$refPath]) {
114+
const cache = dereferencedCache[$refPath];
115+
116+
const refKeys = Object.keys($ref);
117+
if (refKeys.length > 1) {
118+
const extraKeys = {};
119+
for (let key of refKeys) {
120+
if (key !== "$ref" && !(key in cache.value)) {
121+
extraKeys[key] = $ref[key];
122+
}
123+
}
124+
return {
125+
circular: cache.circular,
126+
value: Object.assign({}, cache.value, extraKeys),
127+
};
128+
}
129+
130+
return cache;
131+
}
132+
133+
105134
let pointer = $refs._resolve($refPath, path, options);
106135

107136
if (pointer === null) {
@@ -122,7 +151,7 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
122151
// Crawl the dereferenced value (unless it's circular)
123152
if (!circular) {
124153
// Determine if the dereferenced value is circular
125-
let dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, $refs, options);
154+
let dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options);
126155
circular = dereferenced.circular;
127156
dereferencedValue = dereferenced.value;
128157
}
@@ -138,10 +167,18 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
138167
dereferencedValue.$ref = pathFromRoot;
139168
}
140169

141-
return {
170+
171+
const dereferencedObject = {
142172
circular,
143173
value: dereferencedValue
144174
};
175+
176+
// only cache if no extra properties than $ref
177+
if (Object.keys($ref).length === 1) {
178+
dereferencedCache[$refPath] = dereferencedObject;
179+
}
180+
181+
return dereferencedObject;
145182
}
146183

147184
/**

0 commit comments

Comments
 (0)