@@ -54,7 +54,7 @@ public object Deserialize(string requestBody)
54
54
var document = bodyJToken . ToObject < Document > ( ) ;
55
55
56
56
_jsonApiContext . DocumentMeta = document . Meta ;
57
- var entity = DocumentToObject ( document . Data ) ;
57
+ var entity = DocumentToObject ( document . Data , document . Included ) ;
58
58
return entity ;
59
59
}
60
60
catch ( Exception e )
@@ -95,8 +95,8 @@ public List<TEntity> DeserializeList<TEntity>(string requestBody)
95
95
var deserializedList = new List < TEntity > ( ) ;
96
96
foreach ( var data in documents . Data )
97
97
{
98
- var entity = DocumentToObject ( data ) ;
99
- deserializedList . Add ( ( TEntity ) entity ) ;
98
+ var entity = ( TEntity ) DocumentToObject ( data , documents . Included ) ;
99
+ deserializedList . Add ( entity ) ;
100
100
}
101
101
102
102
return deserializedList ;
@@ -107,7 +107,7 @@ public List<TEntity> DeserializeList<TEntity>(string requestBody)
107
107
}
108
108
}
109
109
110
- public object DocumentToObject ( DocumentData data )
110
+ public object DocumentToObject ( DocumentData data , List < DocumentData > included = null )
111
111
{
112
112
if ( data == null ) throw new JsonApiException ( 422 , "Failed to deserialize document as json:api." ) ;
113
113
@@ -117,7 +117,7 @@ public object DocumentToObject(DocumentData data)
117
117
var entity = Activator . CreateInstance ( contextEntity . EntityType ) ;
118
118
119
119
entity = SetEntityAttributes ( entity , contextEntity , data . Attributes ) ;
120
- entity = SetRelationships ( entity , contextEntity , data . Relationships ) ;
120
+ entity = SetRelationships ( entity , contextEntity , data . Relationships , included ) ;
121
121
122
122
var identifiableEntity = ( IIdentifiable ) entity ;
123
123
@@ -172,7 +172,8 @@ private object DeserializeComplexType(JContainer obj, Type targetType)
172
172
private object SetRelationships (
173
173
object entity ,
174
174
ContextEntity contextEntity ,
175
- Dictionary < string , RelationshipData > relationships )
175
+ Dictionary < string , RelationshipData > relationships ,
176
+ List < DocumentData > included = null )
176
177
{
177
178
if ( relationships == null || relationships . Count == 0 )
178
179
return entity ;
@@ -182,8 +183,8 @@ private object SetRelationships(
182
183
foreach ( var attr in contextEntity . Relationships )
183
184
{
184
185
entity = attr . IsHasOne
185
- ? SetHasOneRelationship ( entity , entityProperties , ( HasOneAttribute ) attr , contextEntity , relationships )
186
- : SetHasManyRelationship ( entity , entityProperties , attr , contextEntity , relationships ) ;
186
+ ? SetHasOneRelationship ( entity , entityProperties , ( HasOneAttribute ) attr , contextEntity , relationships , included )
187
+ : SetHasManyRelationship ( entity , entityProperties , attr , contextEntity , relationships , included ) ;
187
188
}
188
189
189
190
return entity ;
@@ -193,39 +194,53 @@ private object SetHasOneRelationship(object entity,
193
194
PropertyInfo [ ] entityProperties ,
194
195
HasOneAttribute attr ,
195
196
ContextEntity contextEntity ,
196
- Dictionary < string , RelationshipData > relationships )
197
+ Dictionary < string , RelationshipData > relationships ,
198
+ List < DocumentData > included = null )
197
199
{
198
200
var relationshipName = attr . PublicRelationshipName ;
199
201
200
- if ( relationships . TryGetValue ( relationshipName , out RelationshipData relationshipData ) )
201
- {
202
- var relationshipAttr = _jsonApiContext . RequestEntity . Relationships
203
- . SingleOrDefault ( r => r . PublicRelationshipName == relationshipName ) ;
202
+ if ( relationships . TryGetValue ( relationshipName , out RelationshipData relationshipData ) == false )
203
+ return entity ;
204
+
205
+ var relationshipAttr = _jsonApiContext . RequestEntity . Relationships
206
+ . SingleOrDefault ( r => r . PublicRelationshipName == relationshipName ) ;
204
207
205
- if ( relationshipAttr == null )
206
- throw new JsonApiException ( 400 , $ "{ _jsonApiContext . RequestEntity . EntityName } does not contain a relationship '{ relationshipName } '") ;
208
+ if ( relationshipAttr == null )
209
+ throw new JsonApiException ( 400 , $ "{ _jsonApiContext . RequestEntity . EntityName } does not contain a relationship '{ relationshipName } '") ;
207
210
208
- var rio = ( ResourceIdentifierObject ) relationshipData . ExposedData ;
211
+ var rio = ( ResourceIdentifierObject ) relationshipData . ExposedData ;
209
212
210
- var foreignKey = attr . IdentifiablePropertyName ;
211
- var entityProperty = entityProperties . FirstOrDefault ( p => p . Name == foreignKey ) ;
212
- if ( entityProperty == null && rio != null )
213
- throw new JsonApiException ( 400 , $ "{ contextEntity . EntityType . Name } does not contain a foreign key property '{ foreignKey } ' for has one relationship '{ attr . InternalRelationshipName } '") ;
213
+ var foreignKey = attr . IdentifiablePropertyName ;
214
+ var foreignKeyProperty = entityProperties . FirstOrDefault ( p => p . Name == foreignKey ) ;
214
215
215
- if ( entityProperty != null )
216
- {
217
- // e.g. PATCH /articles
218
- // {... { "relationships":{ "Owner": { "data" :null } } } }
219
- if ( rio == null && Nullable . GetUnderlyingType ( entityProperty . PropertyType ) == null )
220
- throw new JsonApiException ( 400 , $ "Cannot set required relationship identifier '{ attr . IdentifiablePropertyName } ' to null.") ;
216
+ if ( foreignKeyProperty == null && rio == null )
217
+ return entity ;
221
218
222
- var newValue = rio ? . Id ?? null ;
223
- var convertedValue = TypeHelper . ConvertType ( newValue , entityProperty . PropertyType ) ;
219
+ if ( foreignKeyProperty == null && rio != null )
220
+ throw new JsonApiException ( 400 , $ " { contextEntity . EntityType . Name } does not contain a foreign key property ' { foreignKey } ' for has one relationship ' { attr . InternalRelationshipName } '" ) ;
224
221
225
- _jsonApiContext . RelationshipsToUpdate [ relationshipAttr ] = convertedValue ;
222
+ // e.g. PATCH /articles
223
+ // {... { "relationships":{ "Owner": { "data": null } } } }
224
+ if ( rio == null && Nullable . GetUnderlyingType ( foreignKeyProperty . PropertyType ) == null )
225
+ throw new JsonApiException ( 400 , $ "Cannot set required relationship identifier '{ attr . IdentifiablePropertyName } ' to null because it is a non-nullable type.") ;
226
226
227
- entityProperty . SetValue ( entity , convertedValue ) ;
228
- }
227
+ var newValue = rio ? . Id ?? null ;
228
+ var convertedValue = TypeHelper . ConvertType ( newValue , foreignKeyProperty . PropertyType ) ;
229
+
230
+ _jsonApiContext . RelationshipsToUpdate [ relationshipAttr ] = convertedValue ;
231
+
232
+ foreignKeyProperty . SetValue ( entity , convertedValue ) ;
233
+
234
+
235
+ if ( rio != null
236
+ // if the resource identifier is null, there should be no reason to instantiate an instance
237
+ && rio . Id != null )
238
+ {
239
+ // we have now set the FK property on the resource, now we need to check to see if the
240
+ // related entity was included in the payload and update its attributes
241
+ var includedRelationshipObject = GetIncludedRelationship ( rio , included , relationshipAttr ) ;
242
+ if ( includedRelationshipObject != null )
243
+ relationshipAttr . SetValue ( entity , includedRelationshipObject ) ;
229
244
}
230
245
231
246
return entity ;
@@ -235,7 +250,8 @@ private object SetHasManyRelationship(object entity,
235
250
PropertyInfo [ ] entityProperties ,
236
251
RelationshipAttribute attr ,
237
252
ContextEntity contextEntity ,
238
- Dictionary < string , RelationshipData > relationships )
253
+ Dictionary < string , RelationshipData > relationships ,
254
+ List < DocumentData > included = null )
239
255
{
240
256
var relationshipName = attr . PublicRelationshipName ;
241
257
@@ -245,14 +261,13 @@ private object SetHasManyRelationship(object entity,
245
261
246
262
if ( data == null ) return entity ;
247
263
248
- var relationshipShells = relationshipData . ManyData . Select ( r =>
264
+ var relatedResources = relationshipData . ManyData . Select ( r =>
249
265
{
250
- var instance = attr . Type . New < IIdentifiable > ( ) ;
251
- instance . StringId = r . Id ;
266
+ var instance = GetIncludedRelationship ( r , included , attr ) ;
252
267
return instance ;
253
268
} ) ;
254
269
255
- var convertedCollection = TypeHelper . ConvertCollection ( relationshipShells , attr . Type ) ;
270
+ var convertedCollection = TypeHelper . ConvertCollection ( relatedResources , attr . Type ) ;
256
271
257
272
attr . SetValue ( entity , convertedCollection ) ;
258
273
@@ -261,5 +276,41 @@ private object SetHasManyRelationship(object entity,
261
276
262
277
return entity ;
263
278
}
279
+
280
+ private IIdentifiable GetIncludedRelationship ( ResourceIdentifierObject relatedResourceIdentifier , List < DocumentData > includedResources , RelationshipAttribute relationshipAttr )
281
+ {
282
+ // at this point we can be sure the relationshipAttr.Type is IIdentifiable because we were able to successfully build the ContextGraph
283
+ var relatedInstance = relationshipAttr . Type . New < IIdentifiable > ( ) ;
284
+ relatedInstance . StringId = relatedResourceIdentifier . Id ;
285
+
286
+ // can't provide any more data other than the rio since it is not contained in the included section
287
+ if ( includedResources == null || includedResources . Count == 0 )
288
+ return relatedInstance ;
289
+
290
+ var includedResource = GetLinkedResource ( relatedResourceIdentifier , includedResources ) ;
291
+ if ( includedResource == null )
292
+ return relatedInstance ;
293
+
294
+ var contextEntity = _jsonApiContext . ContextGraph . GetContextEntity ( relationshipAttr . Type ) ;
295
+ if ( contextEntity == null )
296
+ throw new JsonApiException ( 400 , $ "Included type '{ relationshipAttr . Type } ' is not a registered json:api resource.") ;
297
+
298
+ SetEntityAttributes ( relatedInstance , contextEntity , includedResource . Attributes ) ;
299
+
300
+ return relatedInstance ;
301
+ }
302
+
303
+ private DocumentData GetLinkedResource ( ResourceIdentifierObject relatedResourceIdentifier , List < DocumentData > includedResources )
304
+ {
305
+ try
306
+ {
307
+ return includedResources . SingleOrDefault ( r => r . Type == relatedResourceIdentifier . Type && r . Id == relatedResourceIdentifier . Id ) ;
308
+ }
309
+ catch ( InvalidOperationException e )
310
+ {
311
+ throw new JsonApiException ( 400 , $ "A compound document MUST NOT include more than one resource object for each type and id pair."
312
+ + $ "The duplicate pair was '{ relatedResourceIdentifier . Type } , { relatedResourceIdentifier . Id } '", e ) ;
313
+ }
314
+ }
264
315
}
265
316
}
0 commit comments