Skip to content

Commit 428f376

Browse files
committed
Documentbuilder behavior that allows null handling configuration via options
1 parent 5f3947e commit 428f376

8 files changed

+142
-25
lines changed

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,7 @@ public class DocumentBuilder : IDocumentBuilder
1515
private readonly IRequestMeta _requestMeta;
1616
private readonly DocumentBuilderOptions _documentBuilderOptions = new DocumentBuilderOptions();
1717

18-
public DocumentBuilder(IJsonApiContext jsonApiContext)
19-
{
20-
_jsonApiContext = jsonApiContext;
21-
_contextGraph = jsonApiContext.ContextGraph;
22-
}
23-
24-
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
25-
{
26-
_jsonApiContext = jsonApiContext;
27-
_contextGraph = jsonApiContext.ContextGraph;
28-
_requestMeta = requestMeta;
29-
}
30-
31-
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta, IDocumentBuilderBehavior documentBuilderBehavior)
18+
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta=null, IDocumentBuilderBehavior documentBuilderBehavior=null)
3219
{
3320
_jsonApiContext = jsonApiContext;
3421
_contextGraph = jsonApiContext.ContextGraph;
@@ -150,7 +137,7 @@ private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue)
150137

151138
private bool ExcludeNullValuedAttribute(AttrAttribute attr, object attributeValue)
152139
{
153-
return attributeValue == null && _documentBuilderOptions.ExcludeNullValuedAttributes;
140+
return attributeValue == null && _documentBuilderOptions.OmitNullValuedAttributes;
154141
}
155142

156143
private void AddRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using JsonApiDotNetCore.Services;
5+
using Microsoft.AspNetCore.Http;
6+
7+
namespace JsonApiDotNetCore.Builders
8+
{
9+
public class DocumentBuilderBehavior : IDocumentBuilderBehavior
10+
{
11+
private readonly IJsonApiContext _jsonApiContext;
12+
private readonly IHttpContextAccessor _httpContextAccessor;
13+
14+
public DocumentBuilderBehavior(IJsonApiContext jsonApiContext, IHttpContextAccessor httpContextAccessor)
15+
{
16+
this._jsonApiContext = jsonApiContext;
17+
this._httpContextAccessor = httpContextAccessor;
18+
}
19+
20+
public DocumentBuilderOptions GetDocumentBuilderOptions()
21+
{
22+
var nullAttributeResponseBehaviorConfig = this._jsonApiContext.Options.NullAttributeResponseBehavior;
23+
if (nullAttributeResponseBehaviorConfig.AllowClientOverride && _httpContextAccessor.HttpContext.Request.Query.TryGetValue("omitNullValuedAttributes", out var omitNullValuedAttributesQs))
24+
{
25+
if (bool.TryParse(omitNullValuedAttributesQs, out var omitNullValuedAttributes))
26+
{
27+
return new DocumentBuilderOptions(omitNullValuedAttributes);
28+
}
29+
}
30+
return new DocumentBuilderOptions(this._jsonApiContext.Options.NullAttributeResponseBehavior.OmitNullValuedAttributes);
31+
}
32+
}
33+
}

src/JsonApiDotNetCore/Builders/DocumentBuilderOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ namespace JsonApiDotNetCore.Builders
66
{
77
public struct DocumentBuilderOptions
88
{
9-
public DocumentBuilderOptions(bool excludeNullValuedAttributes = false)
9+
public DocumentBuilderOptions(bool omitNullValuedAttributes = false)
1010
{
11-
this.ExcludeNullValuedAttributes = excludeNullValuedAttributes;
11+
this.OmitNullValuedAttributes = omitNullValuedAttributes;
1212
}
1313

14-
public bool ExcludeNullValuedAttributes { get; private set; }
14+
public bool OmitNullValuedAttributes { get; private set; }
1515
}
1616
}

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class JsonApiOptions
1717
public IContextGraph ContextGraph { get; set; }
1818
public bool RelativeLinks { get; set; }
1919
public bool AllowCustomQueryParameters { get; set; }
20+
public NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; }
2021

2122
[Obsolete("JsonContract resolver can now be set on SerializerSettings.")]
2223
public IContractResolver JsonContractResolver
@@ -29,6 +30,7 @@ public IContractResolver JsonContractResolver
2930
NullValueHandling = NullValueHandling.Ignore,
3031
ContractResolver = new DasherizedResolver()
3132
};
33+
3234
internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder();
3335

3436
public void BuildContextGraph<TContext>(Action<IContextGraphBuilder> builder) where TContext : DbContext
@@ -49,4 +51,6 @@ public void BuildContextGraph(Action<IContextGraphBuilder> builder)
4951
ContextGraph = ContextGraphBuilder.Build();
5052
}
5153
}
54+
55+
5256
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace JsonApiDotNetCore.Configuration
6+
{
7+
public struct NullAttributeResponseBehavior
8+
{
9+
public NullAttributeResponseBehavior(bool omitNullValuedAttributes = false, bool allowClientOverride = false)
10+
{
11+
OmitNullValuedAttributes = omitNullValuedAttributes;
12+
AllowClientOverride = allowClientOverride;
13+
}
14+
15+
public bool OmitNullValuedAttributes { get; }
16+
public bool AllowClientOverride { get; }
17+
// ...
18+
}
19+
}

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public static void AddJsonApiInternals(
112112
services.AddScoped<IQueryAccessor, QueryAccessor>();
113113
services.AddScoped<IQueryParser, QueryParser>();
114114
services.AddScoped<IControllerContext, Services.ControllerContext>();
115+
services.AddScoped<IDocumentBuilderBehavior, DocumentBuilderBehavior>();
115116
}
116117

117118
public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using JsonApiDotNetCore.Builders;
5+
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Services;
7+
using Microsoft.AspNetCore.Http;
8+
using Moq;
9+
using Xunit;
10+
11+
namespace UnitTests.Builders
12+
{
13+
public class DocumentBuilderBehaviour_Tests
14+
{
15+
16+
[Theory]
17+
[InlineData(null, null, null, false)]
18+
[InlineData(false, null, null, false)]
19+
[InlineData(true, null, null, true)]
20+
[InlineData(false, false, "true", false)]
21+
[InlineData(false, true, "true", true)]
22+
[InlineData(true, true, "false", false)]
23+
[InlineData(true, false, "false", true)]
24+
[InlineData(null, false, "false", false)]
25+
[InlineData(null, false, "true", false)]
26+
[InlineData(null, true, "true", true)]
27+
[InlineData(null, true, "false", false)]
28+
[InlineData(null, true, "foo", false)]
29+
[InlineData(null, false, "foo", false)]
30+
[InlineData(true, true, "foo", true)]
31+
[InlineData(true, false, "foo", true)]
32+
[InlineData(null, true, null, false)]
33+
[InlineData(null, false, null, false)]
34+
public void CheckNullBehaviorCombination(bool? omitNullValuedAttributes, bool? allowClientOverride, string clientOverride, bool omitsNulls)
35+
{
36+
37+
NullAttributeResponseBehavior nullAttributeResponseBehavior;
38+
if (omitNullValuedAttributes.HasValue && allowClientOverride.HasValue)
39+
{
40+
nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value, allowClientOverride.Value);
41+
}else if (omitNullValuedAttributes.HasValue)
42+
{
43+
nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value);
44+
}else if
45+
(allowClientOverride.HasValue)
46+
{
47+
nullAttributeResponseBehavior = new NullAttributeResponseBehavior(allowClientOverride: allowClientOverride.Value);
48+
}
49+
else
50+
{
51+
nullAttributeResponseBehavior = new NullAttributeResponseBehavior();
52+
}
53+
54+
var jsonApiContextMock = new Mock<IJsonApiContext>();
55+
jsonApiContextMock.SetupGet(m => m.Options)
56+
.Returns(new JsonApiOptions() {NullAttributeResponseBehavior = nullAttributeResponseBehavior});
57+
58+
var httpContext = new DefaultHttpContext();
59+
if (clientOverride != null)
60+
{
61+
httpContext.Request.QueryString = new QueryString($"?omitNullValuedAttributes={clientOverride}");
62+
}
63+
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
64+
httpContextAccessorMock.SetupGet(m => m.HttpContext).Returns(httpContext);
65+
66+
var sut = new DocumentBuilderBehavior(jsonApiContextMock.Object, httpContextAccessorMock.Object);
67+
var documentBuilderOptions = sut.GetDocumentBuilderOptions();
68+
69+
Assert.Equal(omitsNulls, documentBuilderOptions.OmitNullValuedAttributes);
70+
}
71+
72+
}
73+
}

test/UnitTests/Builders/DocumentBuilder_Tests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,18 @@ public void Build_Can_Build_CustomIEnumerables()
148148
[InlineData(null,null,true)]
149149
[InlineData(false,null,true)]
150150
[InlineData(true,null,false)]
151-
[InlineData(null,"foo",true)]
152-
[InlineData(false,"foo",true)]
153-
[InlineData(true,"foo",true)]
151+
[InlineData(null,"foo",true)]
152+
[InlineData(false,"foo",true)]
153+
[InlineData(true,"foo",true)]
154154
public void DocumentBuilderOptions(bool? excludeNullValuedAttributes,
155-
string attributeValue,
155+
string attributeValue,
156156
bool resultContainsAttribute)
157157
{
158-
var documentBuilderBehaviourMock = new Mock<IDocumentBuilderBehavior>();
158+
var documentBuilderBehaviourMock = new Mock<IDocumentBuilderBehavior>();
159159
if (excludeNullValuedAttributes.HasValue)
160160
{
161-
documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions())
162-
.Returns(new DocumentBuilderOptions(excludeNullValuedAttributes.Value));
161+
documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions())
162+
.Returns(new DocumentBuilderOptions(excludeNullValuedAttributes.Value));
163163
}
164164
var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, null, excludeNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null);
165165
var document = documentBuilder.Build(new Model(){StringProperty = attributeValue});

0 commit comments

Comments
 (0)