@@ -54,7 +54,7 @@ public object Deserialize(string requestBody)
5454 var document = bodyJToken . ToObject < Document > ( ) ;
5555
5656 _jsonApiContext . DocumentMeta = document . Meta ;
57- var entity = DocumentToObject ( document . Data ) ;
57+ var entity = DocumentToObject ( document . Data , document . Included ) ;
5858 return entity ;
5959 }
6060 catch ( Exception e )
@@ -95,8 +95,8 @@ public List<TEntity> DeserializeList<TEntity>(string requestBody)
9595 var deserializedList = new List < TEntity > ( ) ;
9696 foreach ( var data in documents . Data )
9797 {
98- var entity = DocumentToObject ( data ) ;
99- deserializedList . Add ( ( TEntity ) entity ) ;
98+ var entity = ( TEntity ) DocumentToObject ( data , documents . Included ) ;
99+ deserializedList . Add ( entity ) ;
100100 }
101101
102102 return deserializedList ;
@@ -107,7 +107,7 @@ public List<TEntity> DeserializeList<TEntity>(string requestBody)
107107 }
108108 }
109109
110- public object DocumentToObject ( DocumentData data )
110+ public object DocumentToObject ( DocumentData data , List < DocumentData > included = null )
111111 {
112112 if ( data == null ) throw new JsonApiException ( 422 , "Failed to deserialize document as json:api." ) ;
113113
@@ -117,7 +117,7 @@ public object DocumentToObject(DocumentData data)
117117 var entity = Activator . CreateInstance ( contextEntity . EntityType ) ;
118118
119119 entity = SetEntityAttributes ( entity , contextEntity , data . Attributes ) ;
120- entity = SetRelationships ( entity , contextEntity , data . Relationships ) ;
120+ entity = SetRelationships ( entity , contextEntity , data . Relationships , included ) ;
121121
122122 var identifiableEntity = ( IIdentifiable ) entity ;
123123
@@ -172,7 +172,8 @@ private object DeserializeComplexType(JContainer obj, Type targetType)
172172 private object SetRelationships (
173173 object entity ,
174174 ContextEntity contextEntity ,
175- Dictionary < string , RelationshipData > relationships )
175+ Dictionary < string , RelationshipData > relationships ,
176+ List < DocumentData > included = null )
176177 {
177178 if ( relationships == null || relationships . Count == 0 )
178179 return entity ;
@@ -182,8 +183,8 @@ private object SetRelationships(
182183 foreach ( var attr in contextEntity . Relationships )
183184 {
184185 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 ) ;
187188 }
188189
189190 return entity ;
@@ -193,39 +194,53 @@ private object SetHasOneRelationship(object entity,
193194 PropertyInfo [ ] entityProperties ,
194195 HasOneAttribute attr ,
195196 ContextEntity contextEntity ,
196- Dictionary < string , RelationshipData > relationships )
197+ Dictionary < string , RelationshipData > relationships ,
198+ List < DocumentData > included = null )
197199 {
198200 var relationshipName = attr . PublicRelationshipName ;
199201
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 ) ;
204207
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 } '") ;
207210
208- var rio = ( ResourceIdentifierObject ) relationshipData . ExposedData ;
211+ var rio = ( ResourceIdentifierObject ) relationshipData . ExposedData ;
209212
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 ) ;
214215
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 ;
221218
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 } '" ) ;
224221
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.") ;
226226
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 ) ;
229244 }
230245
231246 return entity ;
@@ -235,7 +250,8 @@ private object SetHasManyRelationship(object entity,
235250 PropertyInfo [ ] entityProperties ,
236251 RelationshipAttribute attr ,
237252 ContextEntity contextEntity ,
238- Dictionary < string , RelationshipData > relationships )
253+ Dictionary < string , RelationshipData > relationships ,
254+ List < DocumentData > included = null )
239255 {
240256 var relationshipName = attr . PublicRelationshipName ;
241257
@@ -245,14 +261,13 @@ private object SetHasManyRelationship(object entity,
245261
246262 if ( data == null ) return entity ;
247263
248- var relationshipShells = relationshipData . ManyData . Select ( r =>
264+ var relatedResources = relationshipData . ManyData . Select ( r =>
249265 {
250- var instance = attr . Type . New < IIdentifiable > ( ) ;
251- instance . StringId = r . Id ;
266+ var instance = GetIncludedRelationship ( r , included , attr ) ;
252267 return instance ;
253268 } ) ;
254269
255- var convertedCollection = TypeHelper . ConvertCollection ( relationshipShells , attr . Type ) ;
270+ var convertedCollection = TypeHelper . ConvertCollection ( relatedResources , attr . Type ) ;
256271
257272 attr . SetValue ( entity , convertedCollection ) ;
258273
@@ -261,5 +276,41 @@ private object SetHasManyRelationship(object entity,
261276
262277 return entity ;
263278 }
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+ }
264315 }
265316}
0 commit comments