Skip to content

Commit 2b3aecb

Browse files
committed
fix(*): allow null data objects
Issue #21 - 2
1 parent e966f66 commit 2b3aecb

File tree

7 files changed

+72
-29
lines changed

7 files changed

+72
-29
lines changed

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
132132
var navigationEntity = _jsonApiContext.ContextGraph
133133
.GetRelationship(entity, r.RelationshipName);
134134

135-
if (navigationEntity is IEnumerable)
135+
if(navigationEntity == null)
136+
relationshipData.SingleData = null;
137+
else if (navigationEntity is IEnumerable)
136138
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.RelationshipName);
137139
else
138140
relationshipData.SingleData = _getRelationship(navigationEntity, r.RelationshipName);
@@ -164,6 +166,8 @@ private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IId
164166

165167
private DocumentData _getIncludedEntity(IIdentifiable entity)
166168
{
169+
if(entity == null) return null;
170+
167171
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType());
168172

169173
var data = new DocumentData

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Threading.Tasks;
@@ -126,9 +127,6 @@ public virtual async Task<IActionResult> GetRelationshipAsync(TId id, string rel
126127
var relationship = _jsonApiContext.ContextGraph
127128
.GetRelationship<T>(entity, relationshipName);
128129

129-
if (relationship == null)
130-
return NotFound();
131-
132130
return Ok(relationship);
133131
}
134132

src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Text;
33
using System.Threading.Tasks;
44
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
56
using JsonApiDotNetCore.Serialization;
67
using JsonApiDotNetCore.Services;
78
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -65,23 +66,35 @@ private T GetService<T>(OutputFormatterWriteContext context)
6566

6667
private string GetResponseBody(object responseObject, IJsonApiContext jsonApiContext, ILogger logger)
6768
{
69+
if (responseObject == null)
70+
return GetNullDataResponse();
71+
6872
if (responseObject.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null)
73+
return GetErrorJson(responseObject, logger);
74+
75+
return JsonApiSerializer.Serialize(responseObject, jsonApiContext);
76+
}
77+
78+
private string GetNullDataResponse()
79+
{
80+
return JsonConvert.SerializeObject(new Document
6981
{
70-
if (responseObject.GetType() == typeof(Error))
71-
{
72-
var errors = new ErrorCollection();
73-
errors.Add((Error)responseObject);
74-
return errors.GetJson();
75-
}
76-
else
77-
{
78-
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
79-
return JsonConvert.SerializeObject(responseObject);
80-
}
82+
Data = null
83+
});
84+
}
85+
86+
private string GetErrorJson(object responseObject, ILogger logger)
87+
{
88+
if (responseObject.GetType() == typeof(Error))
89+
{
90+
var errors = new ErrorCollection();
91+
errors.Add((Error)responseObject);
92+
return errors.GetJson();
8193
}
8294
else
8395
{
84-
return JsonApiSerializer.Serialize(responseObject, jsonApiContext);
96+
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
97+
return JsonConvert.SerializeObject(responseObject);
8598
}
8699
}
87100
}

src/JsonApiDotNetCore/Internal/ContextGraph.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public object GetRelationship<TParent>(TParent entity, string relationshipName)
3333
.FirstOrDefault(p => p.Name.ToLower() == relationshipName.ToLower());
3434

3535
if(navigationProperty == null)
36-
return null;
36+
throw new JsonApiException("400", $"{parentEntityType} does not contain a relationship named {relationshipName}");
3737

3838
return navigationProperty.GetValue(entity);
3939
}

src/JsonApiDotNetCore/Models/DocumentBase.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@
44
namespace JsonApiDotNetCore.Models
55
{
66
public class DocumentBase
7-
{
7+
{
88
[JsonProperty("included")]
99
public List<DocumentData> Included { get; set; }
10+
11+
[JsonProperty("meta")]
1012
public Dictionary<string, object> Meta { get; set; }
13+
14+
// http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
15+
public bool ShouldSerializeIncluded()
16+
{
17+
return (Included != null);
18+
}
19+
20+
public bool ShouldSerializeMeta()
21+
{
22+
return (Meta != null);
23+
}
1124
}
1225
}

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,16 @@ private static object DataToObject(DocumentData data, IJsonApiContext context)
4141

4242
var contextEntity = context.ContextGraph.GetContextEntity(entityTypeName);
4343
context.RequestEntity = contextEntity;
44-
44+
4545
var entity = Activator.CreateInstance(contextEntity.EntityType);
46-
46+
4747
entity = _setEntityAttributes(entity, contextEntity, data.Attributes);
4848
entity = _setRelationships(entity, contextEntity, data.Relationships);
4949

5050
var identifiableEntity = (IIdentifiable)entity;
5151

52-
if(data.Id != null)
53-
identifiableEntity.Id = Convert.ChangeType(data.Id, identifiableEntity.Id.GetType());
52+
if (data.Id != null)
53+
identifiableEntity.Id = ChangeType(data.Id, identifiableEntity.Id.GetType());
5454

5555
return identifiableEntity;
5656
}
@@ -70,7 +70,7 @@ private static object _setEntityAttributes(
7070
object newValue;
7171
if (attributeValues.TryGetValue(attr.PublicAttributeName.Dasherize(), out newValue))
7272
{
73-
var convertedValue = Convert.ChangeType(newValue, entityProperty.PropertyType);
73+
var convertedValue = ChangeType(newValue, entityProperty.PropertyType);
7474
entityProperty.SetValue(entity, convertedValue);
7575
}
7676
}
@@ -92,22 +92,37 @@ private static object _setRelationships(
9292

9393
if (entityProperty == null)
9494
throw new JsonApiException("400", $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.RelationshipName}");
95-
95+
9696
var relationshipName = attr.RelationshipName.Dasherize();
9797
RelationshipData relationshipData;
9898
if (relationships.TryGetValue(relationshipName, out relationshipData))
9999
{
100100
var data = (Dictionary<string, string>)relationshipData.ExposedData;
101-
102-
if(data == null) continue;
103-
101+
102+
if (data == null) continue;
103+
104104
var newValue = data["id"];
105-
var convertedValue = Convert.ChangeType(newValue, entityProperty.PropertyType);
105+
var convertedValue = ChangeType(newValue, entityProperty.PropertyType);
106106
entityProperty.SetValue(entity, convertedValue);
107107
}
108108
}
109109

110110
return entity;
111111
}
112+
113+
private static object ChangeType(object value, Type conversion)
114+
{
115+
var t = conversion;
116+
117+
if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
118+
{
119+
if (value == null)
120+
return null;
121+
122+
t = Nullable.GetUnderlyingType(t);
123+
}
124+
125+
return Convert.ChangeType(value, t);
126+
}
112127
}
113128
}

src/JsonApiDotNetCoreExample/Models/TodoItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class TodoItem : Identifiable<int>
1313
[Attr("ordinal")]
1414
public long Ordinal { get; set; }
1515

16-
public int OwnerId { get; set; }
16+
public int? OwnerId { get; set; }
1717
public virtual Person Owner { get; set; }
1818
}
1919
}

0 commit comments

Comments
 (0)