@@ -138,8 +138,11 @@ function dereference(parser, options) {
138138 * @param {object[]} parents - An array of the parent objects that have already been dereferenced
139139 * @param {$Refs} $refs - The resolved JSON references
140140 * @param {$RefParserOptions} options
141+ * @returns {boolean} - Returns true if a circular reference was found
141142 */
142143function crawl(obj, path, parents, $refs, options) {
144+ var isCircular = false;
145+
143146 if (obj && typeof(obj) === 'object') {
144147 parents.push(obj);
145148
@@ -155,53 +158,83 @@ function crawl(obj, path, parents, $refs, options) {
155158
156159 // Check for circular references
157160 var circular = pointer.circular || parents.indexOf(pointer.value) !== -1;
158- $refs.circular = $refs.circular || circular;
159- if ($refs.circular && !options.$refs.circular) {
160- throw ono.reference('Circular $ref pointer found at %s', keyPath);
161- }
161+ circular && (isCircular = foundCircularReference(keyPath, $refs, options));
162162
163163 // Dereference the JSON reference
164- value = dereference$Ref(obj, key , pointer.value);
164+ var dereferencedValue = getDereferencedValue(value , pointer.value);
165165
166166 // Crawl the dereferenced value (unless it's circular)
167167 if (!circular) {
168- crawl(value, pointer.path, parents, $refs, options);
168+ // If the `crawl` method returns true, then dereferenced value is circular
169+ circular = crawl(dereferencedValue, pointer.path, parents, $refs, options);
170+ isCircular = isCircular || circular;
171+ }
172+
173+ // Replace the JSON reference with the dereferenced value
174+ if (!circular || options.$refs.circular === true) {
175+ obj[key] = dereferencedValue;
169176 }
170177 }
171- else if (parents.indexOf(value) === -1) {
172- crawl(value, keyPath, parents, $refs, options);
178+ else {
179+ if (parents.indexOf(value) === -1) {
180+ isCircular = crawl(value, keyPath, parents, $refs, options);
181+ }
182+ else {
183+ isCircular = foundCircularReference(keyPath, $refs, options);
184+ }
173185 }
174186 });
175187
176188 parents.pop();
177189 }
190+ return isCircular;
178191}
179192
180193/**
181- * Replaces the specified JSON reference with its resolved value .
194+ * Returns the dereferenced value of the given JSON reference .
182195 *
183- * @param {object} obj - The object that contains the JSON reference
184- * @param {string} key - The key of the JSON reference within `obj`
185- * @param {*} value - The resolved value
186- * @returns {*} - Returns the new value of the JSON reference
196+ * @param {object} currentValue - the current value, which contains a JSON reference ("$ref" property)
197+ * @param {*} resolvedValue - the resolved value, which can be any type
198+ * @returns {*} - Returns the dereferenced value
187199 */
188- function dereference$Ref(obj, key, value) {
189- var $refObj = obj[key];
190-
191- if (value && typeof(value) === 'object' && Object.keys($refObj).length > 1) {
192- // The JSON reference has additional properties (other than "$ref"),
200+ function getDereferencedValue(currentValue, resolvedValue) {
201+ if (resolvedValue && typeof(resolvedValue) === 'object' && Object.keys(currentValue).length > 1) {
202+ // The current value has additional properties (other than "$ref"),
193203 // so merge the resolved value rather than completely replacing the reference
194- delete $refObj.$ref;
195- Object.keys(value).forEach(function(key) {
196- if (!(key in $refObj)) {
197- $refObj[key] = value[key];
204+ var merged = {};
205+ Object.keys(currentValue).forEach(function(key) {
206+ if (key !== '$ref') {
207+ merged[key] = currentValue[key];
208+ }
209+ });
210+ Object.keys(resolvedValue).forEach(function(key) {
211+ if (!(key in merged)) {
212+ merged[key] = resolvedValue[key];
198213 }
199214 });
215+ return merged;
200216 }
201217 else {
202218 // Completely replace the original reference with the resolved value
203- return obj[key] = value;
219+ return resolvedValue;
220+ }
221+ }
222+
223+ /**
224+ * Called when a circular reference is found.
225+ * It sets the {@link $Refs#circular} flag, and throws an error if options.$refs.circular is false.
226+ *
227+ * @param {string} keyPath - The JSON Reference path of the circular reference
228+ * @param {$Refs} $refs
229+ * @param {$RefParserOptions} options
230+ * @returns {boolean} - always returns true, to indicate that a circular reference was found
231+ */
232+ function foundCircularReference(keyPath, $refs, options) {
233+ $refs.circular = true;
234+ if (!options.$refs.circular) {
235+ throw ono.reference('Circular $ref pointer found at %s', keyPath);
204236 }
237+ return true;
205238}
206239
207240},{"./pointer":6,"./ref":9,"./util":12,"ono":65,"url":90}],3:[function(require,module,exports){
@@ -542,7 +575,8 @@ function $RefParserOptions(options) {
542575 /**
543576 * Allow circular (recursive) JSON references?
544577 * If false, then a {@link ReferenceError} will be thrown if a circular reference is found.
545- * @type {boolean}
578+ * If "ignore", then circular references will not be dereferenced.
579+ * @type {boolean|string}
546580 */
547581 circular: true
548582 };
@@ -880,20 +914,20 @@ Pointer.join = function(base, tokens) {
880914function resolveIf$Ref(pointer, options) {
881915 // Is the value a JSON reference? (and allowed?)
882916 if ($Ref.isAllowed$Ref(pointer.value, options)) {
883- // Does the JSON reference have other properties (other than " $ref")?
884- // If so, then don't resolve it, since it represents a new type
885- if (Object.keys(pointer.value).length === 1 ) {
886- var $refPath = url.resolve(pointer.path, pointer.value.$ref);
887-
888- if ($refPath === pointer.path) {
889- // The value is a reference to itself, so there's nothing to do.
890- pointer.circular = true;
891- }
892- else {
917+ var $refPath = url.resolve(pointer.path, pointer.value. $ref);
918+
919+ if ($refPath === pointer.path ) {
920+ // The value is a reference to itself, so there's nothing to do.
921+ pointer.circular = true;
922+ }
923+ else {
924+ // Does the JSON reference have other properties (other than "$ref")?
925+ // If so, then don't resolve it, since it represents a new type
926+ if (Object.keys(pointer.value).length === 1) {
893927 // Resolve the reference
894928 var resolved = pointer.$ref.$refs._resolve($refPath);
895929 pointer.$ref = resolved.$ref;
896- pointer.path = resolved.path; // pointer.path = $refPath ???
930+ pointer.path = resolved.path;
897931 pointer.value = resolved.value;
898932 return true;
899933 }
0 commit comments