Skip to content

Commit cb31402

Browse files
Ashley SchauerSeyZ
authored andcommitted
Use recursive tracking through relationship branches to avoid circular reference errors
This allows the serializer to return data instead of null for polymorphic relationships. All tests are passing.
1 parent 65e483a commit cb31402

2 files changed

Lines changed: 329 additions & 33 deletions

File tree

lib/deserializer-utils.js

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ var isFunction = require('lodash/isFunction');
44
var _find = require('lodash/find');
55
var _extend = require('lodash/extend');
66
var _transform = require('lodash/transform');
7-
var _merge = require('lodash/merge');
8-
var _get = require('lodash/get');
9-
var _set = require('lodash/set');
107
var Inflector = require('./inflector');
118

129
module.exports = function (jsonapi, data, opts) {
13-
var alreadyIncluded = {};
10+
var alreadyIncluded = [];
1411

1512
function isComplexType(obj) {
1613
return Array.isArray(obj) || isPlainObject(obj);
@@ -27,34 +24,30 @@ module.exports = function (jsonapi, data, opts) {
2724
}
2825
}
2926

30-
function findIncluded(relationshipData, relationshipName, from) {
27+
function findIncluded(relationshipData, ancestry) {
3128
return new Promise(function (resolve) {
32-
if (!jsonapi.included || !relationshipData) { return resolve(null); }
29+
if (!jsonapi.included || !relationshipData) { resolve(null); }
3330

3431
var included = _find(jsonapi.included, {
3532
id: relationshipData.id,
3633
type: relationshipData.type
3734
});
3835

39-
var path = [
40-
from.type,
41-
from.id,
42-
relationshipName,
43-
relationshipData.type,
44-
relationshipData.id,
45-
]
46-
47-
// Check if the include is already processed (prevent circular
48-
// references).
49-
if (_get(alreadyIncluded, path, false)) {
50-
return resolve(null);
51-
} else {
52-
_merge(alreadyIncluded, _set({}, path, true));
53-
}
54-
5536
if (included) {
37+
// To prevent circular references, check if the record type
38+
// has already been processed in this thread
39+
if (ancestry.indexOf(included.type) > -1) {
40+
return Promise
41+
.all([extractAttributes(included)])
42+
.then(function (results) {
43+
var attributes = results[0];
44+
var relationships = results[1];
45+
resolve(_extend(attributes, relationships));
46+
});
47+
}
48+
5649
return Promise
57-
.all([extractAttributes(included), extractRelationships(included)])
50+
.all([extractAttributes(included), extractRelationships(included, `${ancestry}:${included.type}${included.id}`)])
5851
.then(function (results) {
5952
var attributes = results[0];
6053
var relationships = results[1];
@@ -104,7 +97,7 @@ module.exports = function (jsonapi, data, opts) {
10497
return dest;
10598
}
10699

107-
function extractRelationships(from) {
100+
function extractRelationships(from, ancestry) {
108101
if (!from.relationships) { return; }
109102

110103
var dest = {};
@@ -118,15 +111,15 @@ module.exports = function (jsonapi, data, opts) {
118111
} else if (Array.isArray(relationship.data)) {
119112
return Promise
120113
.all(relationship.data.map(function (relationshipData) {
121-
return extractIncludes(relationshipData, key, from);
114+
return extractIncludes(relationshipData, ancestry);
122115
}))
123116
.then(function (includes) {
124117
if (includes) { dest[keyForAttribute(key)] = includes; }
125118
});
126119
} else {
127-
return extractIncludes(relationship.data, key, from)
128-
.then(function (include) {
129-
if (include) { dest[keyForAttribute(key)] = include; }
120+
return extractIncludes(relationship.data, ancestry)
121+
.then(function (includes) {
122+
if (includes) { dest[keyForAttribute(key)] = includes; }
130123
});
131124
}
132125
}))
@@ -135,8 +128,8 @@ module.exports = function (jsonapi, data, opts) {
135128
});
136129
}
137130

138-
function extractIncludes(relationshipData, relationshipName, from) {
139-
return findIncluded(relationshipData, relationshipName, from)
131+
function extractIncludes(relationshipData, ancestry) {
132+
return findIncluded(relationshipData, ancestry)
140133
.then(function (included) {
141134
var valueForRelationship = getValueForRelationship(relationshipData,
142135
included);
@@ -153,7 +146,7 @@ module.exports = function (jsonapi, data, opts) {
153146

154147
this.perform = function () {
155148
return Promise
156-
.all([extractAttributes(data), extractRelationships(data)])
149+
.all([extractAttributes(data), extractRelationships(data, `${data.type}${data.id}`)])
157150
.then(function (results) {
158151
var attributes = results[0];
159152
var relationships = results[1];
@@ -164,7 +157,6 @@ module.exports = function (jsonapi, data, opts) {
164157
record.links = jsonapi.links;
165158
}
166159

167-
168160
// If option is present, transform record
169161
if (opts && opts.transform) {
170162
record = opts.transform(record);

0 commit comments

Comments
 (0)