diff --git a/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs
index 1ebab6c474..2cf4ef401e 100644
--- a/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs
+++ b/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs
@@ -4,9 +4,16 @@ namespace JsonApiDotNetCore.Models
{
public class ResourceIdentifierObject
{
+ public ResourceIdentifierObject() { }
+ public ResourceIdentifierObject(string type, string id)
+ {
+ Type = type;
+ Id = id;
+ }
+
[JsonProperty("type")]
public string Type { get; set; }
-
+
[JsonProperty("id")]
public string Id { get; set; }
diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs
index 5ad0a2b63d..fc9f11d7e0 100644
--- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs
+++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs
@@ -204,53 +204,59 @@ private object SetHasOneRelationship(object entity,
if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData) == false)
return entity;
- var relationshipAttr = _jsonApiContext.RequestEntity.Relationships
- .SingleOrDefault(r => r.PublicRelationshipName == relationshipName);
-
- if (relationshipAttr == null)
- throw new JsonApiException(400, $"{_jsonApiContext.RequestEntity.EntityName} does not contain a relationship '{relationshipName}'");
-
var rio = (ResourceIdentifierObject)relationshipData.ExposedData;
var foreignKey = attr.IdentifiablePropertyName;
var foreignKeyProperty = entityProperties.FirstOrDefault(p => p.Name == foreignKey);
-
if (foreignKeyProperty == null && rio == null)
return entity;
- if (foreignKeyProperty == null && rio != null)
- throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain a foreign key property '{foreignKey}' for has one relationship '{attr.InternalRelationshipName}'");
-
- // e.g. PATCH /articles
- // {... { "relationships":{ "Owner": { "data": null } } } }
- if (rio == null && Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) == null)
- throw new JsonApiException(400, $"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null because it is a non-nullable type.");
+ SetHasOneForeignKeyValue(entity, attr, foreignKeyProperty, rio);
+ SetHasOneNavigationPropertyValue(entity, attr, rio, included);
- var newValue = rio?.Id ?? null;
- var convertedValue = TypeHelper.ConvertType(newValue, foreignKeyProperty.PropertyType);
+ return entity;
+ }
- _jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;
+ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, PropertyInfo foreignKeyProperty, ResourceIdentifierObject rio)
+ {
+ var foreignKeyPropertyValue = rio?.Id ?? null;
+ if (foreignKeyProperty != null)
+ {
+ // in the case of the HasOne independent side of the relationship, we should still create the shell entity on the other side
+ // we should not actually require the resource to have a foreign key (be the dependent side of the relationship)
- foreignKeyProperty.SetValue(entity, convertedValue);
+ // e.g. PATCH /articles
+ // {... { "relationships":{ "Owner": { "data": null } } } }
+ if (rio == null && Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) == null)
+ throw new JsonApiException(400, $"Cannot set required relationship identifier '{hasOneAttr.IdentifiablePropertyName}' to null because it is a non-nullable type.");
+ var convertedValue = TypeHelper.ConvertType(foreignKeyPropertyValue, foreignKeyProperty.PropertyType);
+ foreignKeyProperty.SetValue(entity, convertedValue);
+ _jsonApiContext.RelationshipsToUpdate[hasOneAttr] = convertedValue;
+ }
+ }
- if (rio != null
- // if the resource identifier is null, there should be no reason to instantiate an instance
- && rio.Id != null)
+ ///
+ /// Sets the value of the navigation property for the related resource.
+ /// If the resource has been included, all attributes will be set.
+ /// If the resource has not been included, only the id will be set.
+ ///
+ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute hasOneAttr, ResourceIdentifierObject rio, List included)
+ {
+ // if the resource identifier is null, there should be no reason to instantiate an instance
+ if (rio != null && rio.Id != null)
{
// we have now set the FK property on the resource, now we need to check to see if the
// related entity was included in the payload and update its attributes
- var includedRelationshipObject = GetIncludedRelationship(rio, included, relationshipAttr);
+ var includedRelationshipObject = GetIncludedRelationship(rio, included, hasOneAttr);
if (includedRelationshipObject != null)
- relationshipAttr.SetValue(entity, includedRelationshipObject);
+ hasOneAttr.SetValue(entity, includedRelationshipObject);
// we need to store the fact that this relationship was included in the payload
// for EF, the repository will use these pointers to make ensure we don't try to
// create resources if they already exist, we just need to create the relationship
- _jsonApiContext.HasOneRelationshipPointers.Add(attr, includedRelationshipObject);
+ _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, includedRelationshipObject);
}
-
- return entity;
}
private object SetHasManyRelationship(object entity,
diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs
index 2da434c765..18d4b0ee6b 100644
--- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs
+++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs
@@ -240,6 +240,7 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel
jsonApiContextMock.SetupAllProperties();
jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary());
+ jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers());
var jsonApiOptions = new JsonApiOptions();
jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions);
@@ -255,7 +256,14 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel
Id = "1",
Attributes = new Dictionary { { "property", property } },
// a common case for this is deserialization in unit tests
- Relationships = new Dictionary { { "dependent", new RelationshipData { } } }
+ Relationships = new Dictionary {
+ {
+ "dependent", new RelationshipData
+ {
+ SingleData = new ResourceIdentifierObject("dependents", "1")
+ }
+ }
+ }
}
};
@@ -267,6 +275,8 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel
// assert
Assert.NotNull(result);
Assert.Equal(property, result.Property);
+ Assert.NotNull(result.Dependent);
+ Assert.Equal(1, result.Dependent.Id);
}
[Fact]