-
-
Notifications
You must be signed in to change notification settings - Fork 158
Feat/#226: Add DocumentBuilderOptions allowing omission of null attributes #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
thanks for this!
Not sure I understand what you mean here. The global configuration allows server applications to opt-in. How would you imagine a client application would opt-in? |
return | ||
(_requestMeta?.GetMeta()?.TryGetValue("jsonapidotnet:omit-null-valued-attributes", out isOmitNullValuedAttributes) ?? false ) ? | ||
(bool)isOmitNullValuedAttributes | ||
: _jsonApiContext.Options.OmitNullValuedAttributesFromResponses; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows a client application to use this feature without explicit permission from the server. I would almost prefer a behavior enum in the options rather than a bool:
enum NullAttributeResponseBehavior {
IncludeNullAttributes = 0, // default
ExcludeNullAttributes = 1,
OptionalForClients = 2
}
or something along those lines. thoughts?
Sounds reasonable, however, would a separate setting that enabled ability for the clients to override the global default be more flexible? It would better cater for schenarios like global: include nulls, client:exclude The latter would be useful for api discovery, if by default server decided to omit nulls. Using the override at development time, clients could easily find out what is available. |
That's fine as long as the server application has 100% control of possible behavior. So, the API you're proposing might look like: struct NullAttributeResponseBehavior {
public NullAttributeResponseBehavior(bool includeNullAttributes = true, bool allowClientOverride = false) {
//...
}
// ...
}
class JsonApiOptions {
public NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; }
= new NullAttributeResponseBehavior()
} I think I would still prefer to have it grouped into a class separate from the |
Cool - the above api sounds good. One question: i'd like to implement IRequestMeta service that would provide access to the meta data in the incoming jsonapi request, just wondering if there is a way to hook this in somehow? Something like this might make a reasonable default implementation as well? It could be then used as a way for the client to specify the null value handling. Does this make sense? { |
Currently, we don't do anything with the meta object during deserialization. It seems like adding something to the public class RequestMetaDocument : IDictionary<string, object>
{
public bool OmitNullValuedAttributes => this.TryGetValue("omit-null-valued-attributes", out var result) ? result : false;
// ...
} and then the
|
Ok, given the above - gave this another go with a different approach. IDocumentBuilderBehavior api can be implemented by the developer and injected, it contains DocumentBuilderOptions that specify how to handle null valued attributes. Also, added the meta element contents from request to IJsonApiContext. |
if (ShouldIncludeAttribute(attr)) | ||
{ | ||
var value = attr.GetValue(entity); | ||
if (value != null || _documentBuilderOptions.IncludeNullAttributes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 things:
- This probably belongs in the
ShouldIncludeAttribute
method, possibly as a call to another private methodAttrIsNotNullAndIsNotExcluded()
(or some better name). - This doesn't take into consider the
DocumentMeta
field, so I'm guessing this is still WIP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaned this up a bit. My plan was to use the DocumentMeta in my own implementation if IDocumentBuilderBehavior - and that the interface could be similar to IRequestMeta in that library users can provide their own implementations. However, it might make sense to include a built in default implementation with the library - which I could do if you like.
{ | ||
public struct DocumentBuilderOptions | ||
{ | ||
public DocumentBuilderOptions(bool isIncludeNullAttributes = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's rename the parameter to includeNullAttributes
where were you planning on adding the setting to allow client overrides? maybe I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - IDocumentBuilderBehaviour allows injecting an implementation that produces document builder options, i.e. an implementation could allow for determining null handling based no the global options and information provided in document meta. So if we want to provide a built in behaviour with the features discussed, i thought the client overrides would be handled in that implementation.
I can solve my current use case with this implementation: I can inject null handling options of my choosing, and access to document meta that allows using client preferences as part of that process. Sweet! Should I include an implementation of IDocumentBuilderBehaviour to be included with the library? Could developers using the library override the behavior with their own? I'm happy to include one if it seems useful. I guess we pretty much know the features that the implementation would have from the above discussion. |
Hey - added an implementation for a document builder behavior.
I actually ended up not using the document.meta, as this is not available for all methods - so perhaps there is no need to expose that, or the exposing might be more suited to another pull request. If clients want to implement some other kind of mechanism for null behaviour, this can be done by service.Replace the default IDocumentBuilderBehaviour with a custom implementation - allowing implementations that use http headers, claims in access tokens or any other implementation specific mechanism. The spec has a section that defines how to name implementation specific query parameters: http://jsonapi.org/format/#query-parameters. I think camelCase used above is compatible but other formats are also possible. Do you have a preference? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all your hard work on this! If you have time, I'd also like to see an end-to-end test. After these changes I'm good with this PR. Which version are you running (2.1.x or 2.2-beta)? I'd like to only do a release to the 2.2-beta, which should be finalized next weekend. But, if you're on 2.1 I can back port it for you. I wouldn't want you to do all this work and not be able to use it immediately 😄
{ | ||
_jsonApiContext = jsonApiContext; | ||
_contextGraph = jsonApiContext.ContextGraph; | ||
_requestMeta = requestMeta; | ||
_documentBuilderOptions = documentBuilderBehavior?.GetDocumentBuilderOptions() ?? _documentBuilderOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend moving the new DocumentBuilderOptions()
call here rather than eagerly allocating in the field initializer which may not be necessary if an instance of IDocumentBuilderBehavior
is being provided by the DI framework. This also reduces misdirection making the code easier to read (i.e. as currently written I see that we're assigning a value to _documentBuilderOptions
but I have to look up to line 16 to see the coalesced value). Super minor and not a blocker for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, it's much better that way.
|
||
public DocumentBuilderBehavior(IJsonApiContext jsonApiContext, IHttpContextAccessor httpContextAccessor) | ||
{ | ||
this._jsonApiContext = jsonApiContext; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this.
is unnecessary and isn't used anywhere else in the library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I'll remove that.
|
||
public bool OmitNullValuedAttributes { get; } | ||
public bool AllowClientOverride { get; } | ||
// ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
☝️
|
||
namespace JsonApiDotNetCore.Builders | ||
{ | ||
public class DocumentBuilderBehavior : IDocumentBuilderBehavior |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class does not define the behavior of the DocumentBuilder
but instead instantiates an options class based on config+request data. The "behavior" suffix is confusing especially with the NullAttributeResponseBehavior
struct whose purpose more closely matches the suffix. I would recommend renaming this class to DocumentBuilderOptionsProvider
, ...Factory
, or ...Service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, makes sense. I'll go with DocumentBuilderOptionsProvider.
… from responses - NullAttributeResponseBehavior option for configuration - Support for global defaults - Support for client override using a query string parameter
Cleaned up the commits, I can have a look at the end to end test bit. We're currently using an internal branch, so from that perspective no need to back port things, but thanks for the offer :) |
Added few end to end test cases related to the null valued attribute handling. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good with this. Added item to #223 to document this behavior. Thanks for the PR!
Nice - thank you for the library! |
Closes #226
FEATURE
Looked into adding support for omitting null valued attributes from responses. This can be enabled globally by setting option 'OmitNullValuedAttributesFromResponses' to true. Default value for the option is false.
To consider before the merge: JsonApi is spec does not specify anything about omitting null values in responses - least as far as I can tell. It may be better to allow clients to opt-in for this functionality instead of having a global configuration, so only compliant clients can opt-in. What do you think?