-
Notifications
You must be signed in to change notification settings - Fork 351
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
Omit null property value in JSON response #1106
base: release-7.x
Are you sure you want to change the base?
Conversation
/// <summary> | ||
/// True if null values are omitted, per the omit-values parameter in Preference header of the message. | ||
/// </summary> | ||
internal bool NullValuesOmitted { get; set; } |
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.
internal [](start = 8, length = 8)
Should we make that public to allow tests in projects using ODL simple injection? Injecting via PreferenceAppliedHeader() can be a bit complicated (to much stuff to mock) #Closed
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 think the NullValuesOmitted is caused by Preference-Applied header parameter value in the specific request. ODataPreferenceHeader(responseMessage).OmitValus = null should do the trick.
If it were the case for DependencyInjection, we would need to do it with bool flag for convenience, but it is not the case here, right? #Closed
this.WriterTestConfigurationProvider.JsonLightFormatConfigurationsWithIndent, | ||
(testDescriptor, testConfiguration) => | ||
{ | ||
testConfiguration.MessageWriterSettings.OmitNullValues = true; |
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.
Could you add EndToEnd test that uses responseMessageWithModel.PreferenceAppliedHeader().OmitValues= "nulls"; to check e2e? Something like tests for AnnotationFilter in WritePayloadTests or InstanceAnnotationWriterIntegrationTests #Closed
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.
Added e2e test WriteJsonWittoutModelTests::ExpandedCustomerEntryOmitNullValuesTest with proper header and null values for various cases. #Closed
For performance reasons consider adding following optimization:
Refers to: src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs:192 in df084b6. [](commit_id = df084b6, deletion_comment = False) |
entries.Add(reader.Item as ODataResource); | ||
break; | ||
case ODataReaderState.NestedResourceInfoStart: | ||
navigationLink = (ODataNestedResourceInfo)reader.Item; |
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.
rename navigationLink to "nestedResourceInfo". #Closed
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.
Note that "navigationLink" is still used for nestedResourceInfo in a number of places
In reply to: 173003599 [](ancestors = 173003599)
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 think you are looking at some original test cases that are not changed by this PR. Since you have spotted them, and it makes sense to renaming it, I have updated the names to 'nestedResourceInfo'.
In reply to: 233550345 [](ancestors = 233550345,173003599)
@@ -353,6 +367,64 @@ public void ReadingTypeDefinitionPayloadJsonLight() | |||
address.Properties.FirstOrDefault(s => string.Equals(s.Name, "CountryRegion", StringComparison.OrdinalIgnoreCase)).Value.Should().Be("China"); | |||
} | |||
|
|||
[Fact] | |||
public void ReadingTypeDefinitionPayloadJsonLightWithOmittedNullValues() |
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.
add a comparable test case of same payload with omitedNullValue = false, expecting different outcome. #Closed
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.
Added false case for comparison for all new tests as well (omittedNullValue=false should be the default case for all old tests).
In reply to: 173004227 [](ancestors = 173004227)
} | ||
|
||
[Fact] | ||
public void ReadingPropertyInTopLevelOrInComplexTypeShouldRestoreOmittedNullValuesWithSelectedPropertiesEntireSubTree() |
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.
Add a test case for dynamic property selected, and it is not in the payload, should be materialized as null.
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.
Add unknown properties to context URI in test cases to verify unknown properties are ignored during deserialization.
Dynamic properties don't show up in context URI in response, therefore omitted one are not materialized as nulls.
In reply to: 173008510 [](ancestors = 173008510)
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 need to look at the code, but this comment seems wrong. Dynamic properties do show up in ContextUrl if explicitly selected.
In reply to: 174266340 [](ancestors = 174266340,173008510)
// resource properties read and navigation properties read. | ||
if (!resource.Properties.Any( | ||
p => p.Name.Equals(currentProperty.Name, StringComparison.Ordinal)) | ||
&& !resourceState.NavigationPropertiesRead.Contains(currentProperty.Name)) |
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.
Make sure we have test case to cover complex type and expanded navigation property. #Resolved
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.
Already have coverage for complex type.
Added coverage for navigation link in e2e test ExpandedCustomerEntryOmitNullValuesTest.
Added various test case for expanded navigation property in PropertyAndValueJsonLightReaderIntegrationTests: expanded navigation link should have no content when the link is null (see sample link: http://services.odata.org/V4/TripPinService/People('ronaldmundy')?$expand=Photo)
In reply to: 173012135 [](ancestors = 173012135)
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 don't think TripPinService is handling this case correctly; if an expanded single-value navigation property is null, I would expect a null value be returned in the payload, and I would expect to get the null value as nested content when I ready the payload. We should make sure that ODL does support reading and writing a single-value null navigation property, and fix this case to handle a missing explicitly selected null navigation property the same as an explicit null value for a nullable single-value navigation property in the payload. Note that navigation properties (null or otherwise) wouldn't be returned int he properties collection of the resource, but rather as nestedInfo. So the logic to handle any missing null-valued nav props should come before adding the final properties in the endentry case.
In reply to: 174645486 [](ancestors = 174645486,173012135)
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.
expanded single-value navigation property would be a nested resource if it is not null; When it is null, the json reader won't be able to enter the resourcestart state since the name token is not in the reponse payload, therefore won't be able to produce a resource item in a natural way. The omitted nested resource item will be null as expected.
In reply to: 186565783 [](ancestors = 186565783,174645486,173012135)
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.
ba5d506
to
7c1dba3
Compare
7c1dba3
to
bc4a274
Compare
@@ -242,6 +242,8 @@ HttpHeaderValueLexer_FailedToReadTokenOrQuotedString=An error occurred when pars | |||
HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a valid separator after a quoted-string. | |||
HttpHeaderValueLexer_EndOfFileAfterSeparator=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the header value should not end with the separator '{3}'. | |||
|
|||
HttpPreferenceHeader_InvalidValueForToken=The value '{0}' is not a valid value for OData token '{1}' in HTTP Prefer or Preference-Applied headers. |
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.
Referencing "tokens" isn't very end-user friendly. How about "The value '{0}' is not a valid value for the preference or preference-applied header '{1}'. #Closed
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.
removed because unknown preference header value should just be ignored.
In reply to: 186522770 [](ancestors = 186522770)
// be applied to the reader's setting. | ||
this.settings.NullValuesOmitted = | ||
omitValuePreferenceApplied.Equals(ODataConstants.OmitValuesNulls, StringComparison.OrdinalIgnoreCase); | ||
} |
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.
The check for the omit-nulls preference in the ODataMessageWriter is more concise. Any reason not to use the same code here? #Closed
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.
Updated to be the same as the writer; should be equivalent except that on reader side this flag should be false by default.
In reply to: 186526760 [](ancestors = 186526760)
} | ||
|
||
// No-ops for other preference values, including "defaults" which is in the OData protocol but not implemented yet. | ||
} |
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.
Should we allow setting of other preference values here, and just ignore later in the stack? Seems odd that this would silently fail to set the value... #Closed
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.
We could, that would require figuring out the value and associated parameters of the omit-values header (see HttpHeaderValueElement c'tor) that we don't have details about or doesn't belong to this task yet. It is typical to silently ignore unrecognizable preference header, and we have inline-comment. Sounds reasonable?
In reply to: 186528628 [](ancestors = 186528628)
Debug.Assert(this.selectedProperties != null, "selectedProperties != null"); | ||
|
||
// Get declared properties selected, and filter out unrecognized properties. | ||
return this.selectedProperties.Select(structuredType.FindProperty).OfType<IEdmProperty>(); |
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.
Why are we omitting undefined properties? If a dynamic property is explicitly requested in the select list, I would expect to return a null value for it if it was a null value or omitted in the payload. #Closed
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.
Added GetSelectedDynamicProperties, invoked by ODataJsonLightReader.RestoreOmittedNullValues seperatly after this for open type nodes. Thanks.
In reply to: 186531887 [](ancestors = 186531887)
// Restore omitted null value only when processing response and when | ||
// Preference header omit-values=nulls is specified. | ||
if (this.jsonLightInputContext.ReadingResponse | ||
&& this.jsonLightInputContext.MessageReaderSettings.NullValuesOmitted) |
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.
should this be encapsulated in a method on ODataReaderCore, similar to the ShouldOmitNullValues on ODataWriterCore? #Closed
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.
Moved to ODataReaderCore.ShouldRestoreNullValues().
In reply to: 186532720 [](ancestors = 186532720)
@@ -202,8 +223,6 @@ private bool IsOpenProperty(ODataProperty property) | |||
|
|||
WriteInstanceAnnotation(property, isTopLevel, currentPropertyInfo.MetadataType.IsUndeclaredProperty); |
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 logic should come before the check for null values. Even if the property is null, it can have instance annotations (in fact, a common use case for instance annotations is annotating a null value with why it is null). Please move this before the check for null values, and add a test to verify that instance annotations are correctly written/read for annotations applied to null values, with and without omit-nulls. #Closed
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.WriteNullProperty(property); | ||
this.WriteNullProperty(property, omitNullValues); |
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.
do you need the new omitNullValues parameter in WriteNullProperty? Why would you get this far if the property was a null value and omitNullValues was true? Is this only for validating the metadata? Can you handle that when you check for isNullValue, and get rid of the new omitNullValues flag to WriteNullProperty? #Closed
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.
The omitNullValues is needed in WriteNullProperty to differentiate delta and others; When we get this far, we knows property is a null but omitNullValues can still be true or false; I think validating metadata is done before this (L215,L217); I believe WriteNullProperty properly encapsulates the logic to write null property, without the omitNullValues flag it wouldn't be able to tell whether null values needs to be serialized (e.g. delata or non-delta).
I have added additional information about the omitNullValues param in pending changes.
In reply to: 186561856 [](ancestors = 186561856)
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.
On L219, if the value is the null value and omitNullValues is true, you would have returned, so you would never reach this line for a null value if omitNullValues is true. Right?
In reply to: 187209557 [](ancestors = 187209557,186561856)
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.
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.
The nested if clause checks two things:
- If this is top level, in which case omitNullValues doesn't apply (it's already ignored in WriteNullProperty)
- whether we should check metadata
For #2, I'm not convinced we should skip checking metadata for null values as this would introduce a change in what the writer was able to write whether omit values was true or false:
a) this would allow the writer to write a non-null value and one or more null values for the same property when omitNullValues was true, which should really not be valid.
b) this would allow the writer to write a null value for an undeclared property, even though throwOnUndeclaredProperty was true, when omitNullValues is true
So, we should simplify move the validations in L227 and L229 before the check for null values in L219, and in L219 return for null non-top level elements if omitNullValues is true. Then we can remove the omitNullValues argument from WriteNullProperty, since it will only be called when we really want to write the null value.
In reply to: 209787942 [](ancestors = 209787942,198078564,187209557,186561856)
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.
For performance reason, kosinsky has requested (see his comment at L192) that skipping the validations at L227-229, using check for null values in L219 before hand. Switching the order as you suggest above would contradict that, right?
In reply to: 213515156 [](ancestors = 213515156,209787942,198078564,187209557,186561856)
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.
Quite honestly, throwOnUndeclaredProperty is a carry-over from OData V3. In OData V4 it should always be false which means this check should never have to occur. Unfortunately, the default value is currently false, but we should consider changing it.
I still dislike having the behavior change based on the omit-nulls flag; having the same code throw an exception if omit-nulls is false and succeed if omit-nulls is true is unintuitive; for example, avoiding the duplicateNamesChecker means that the app could succeed in writing the same property twice, once as null and once not as null, with omit-nulls equal to true, and fail with omit-nulls equal to false. Omit-nulls should we a wire optimization, not a change in behavior.
All that said, it's a nice optimization, even if throwOnUndeclaredProperty is false, to avoid the duplicateNameChecker....
In reply to: 213868365 [](ancestors = 213868365,213515156,209787942,198078564,187209557,186561856)
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.
As we discussed with kosinsky, we can ignore the optimization for validation. I have updated the code accordingly including removal of the omitNullValues argument from WriteNullProperty. Thanks.
In reply to: 229533703 [](ancestors = 229533703,213868365,213515156,209787942,198078564,187209557,186561856)
private void WriteNullProperty( | ||
ODataProperty property) | ||
ODataProperty property, | ||
bool omitNullValues) |
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.
See above. I would think that you can handle all cases of null and omit=null in WriteProperty, and never get to WriteNullProperty for null properties in the omit nulls case. #Closed
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.
For all cases of null and omit=null, code block around L392 is needed for <V7. Am I missing anything?
In reply to: 186562291 [](ancestors = 186562291)
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 only call WriteNullProperty in one place, and you have an optimization before calling writeNullProperty that, if the property is null and omitNullValues is true, you return. So this will only ever be called if it is a null value that you really want to write.
In reply to: 187208933 [](ancestors = 187208933,186562291)
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.
omitNullVallues could be either true or false and needs to be passed in. See related comment in caller.
In reply to: 198079167 [](ancestors = 198079167,187208933,186562291)
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.
see comment in caller. I don't see any reason why we can't check this case before calling the method and would have to pass it in.
In reply to: 209788338 [](ancestors = 209788338,198079167,187208933,186562291)
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.
see response in the caller.
In reply to: 213515321 [](ancestors = 213515321,209788338,198079167,187208933,186562291)
foreach (IEdmProperty currentProperty in selectedProperties) | ||
{ | ||
Debug.Assert(currentProperty.Type != null, "currentProperty.Type != null"); | ||
if (!currentProperty.Type.IsNullable || currentProperty.PropertyKind == EdmPropertyKind.Navigation) |
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.
|| currentProperty.PropertyKind == EdmPropertyKind.Navigation [](start = 65, length = 61)
this seems suspect. Doesn't that mean that we wouldn't return a null value for a nav prop that was explicitly selected? why did we add the nav properties to the selectedProperties list if we just ignore them here? #Resolved
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.
Yes, see comment inside the if block. I believe nav links (@odata.navigationlink annotation) are added by SelectedPropertiesNodes.GetSelectedNavigationProperties method, it looks like a logical method, additionally it has 10 references of src+tests. Filtering out here during the processing of null value restoration should be a reasonable approach?
Nested nav property which is PropertyKind of EdmPropertyKind.Structual could be restored to nested resource, but first thing to do that is to require json reader to create a resource start srate for a resouce that is not present on the wire. Another natural and simpler approach, which is taken here, to handle such case is don't restore the resource, and client will get the same null ODataResource as result.
In reply to: 186563420 [](ancestors = 186563420)
954c798
to
88fa5ef
Compare
@mikepizzo I just pushed latest commit for CR updates above. As we discussed offline, please check whether it is OK to not emitting the null for nested resource omitted on the wire; otherwise we need to make further change related to the overall json reader that could be hacky. Thanks. #Closed |
@mikepizzo I have updated with the latest commit to not omit null values for expanded navigation properties. Can you please take a look? Thanks. #Closed |
@@ -169,6 +169,16 @@ public ODataMessageReader(IODataResponseMessage responseMessage, ODataMessageRea | |||
this.model = model ?? GetModel(this.container); | |||
this.edmTypeResolver = new EdmTypeReaderResolver(this.model, this.settings.ClientCustomTypeResolver); | |||
|
|||
// Whether null values were omitted in the response, false by default. | |||
this.settings.NullValuesOmitted = 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.
why not make this an else for the following if? #Closed
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.
Setting the false as the default value, see comment in the line above.
In reply to: 198055075 [](ancestors = 198055075)
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.
in this case, you are always setting the value to false, and then possibly setting it back to true. Why not just set it to true if the preference apply matches, otherwise set it to false, as in:
if (string.Equals(responseMessage.PreferenceAppliedHeader().OmitValues, ODataConstants.OmitValuesNulls,
StringComparison.OrdinalIgnoreCase))
{
// If the Preference-Applied header's omit-values parameter is present in the response, its value should
// be applied to the reader's setting.
this.settings.NullValuesOmitted = true;
}
else
{
this.settings.NullValuesOmitted = false;
}
In reply to: 209783953 [](ancestors = 209783953,198055075)
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.
These two ways are mathematically equivalent. I still slightly incline to initializing it to false first in order to emphasize the fact that preference header omit-values=nulls is required to be set explicitly.
In reply to: 213509847 [](ancestors = 213509847,209783953,198055075)
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 still prefer setting it once to twice as it feels cleaner, but I'm not going to force it.
In reply to: 213861831 [](ancestors = 213861831,213509847,209783953,198055075)
|
||
if (this.selectionType == SelectionType.EntireSubtree || this.hasWildcard) | ||
{ | ||
return structuredType.DeclaredProperties; |
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.
DeclaredProperties [](start = 38, length = 18)
DeclaredProperties [](start = 38, length = 18)
This only returns the properties declared directly on the type; you should use structuredType.Properties to include inherited properties. #Closed
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.
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.
Why is this by design? If this is a derived type, why are we only restoring properties declared directly on the derived type and not inherited properties?
In reply to: 209788492 [](ancestors = 209788492,198059137)
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.
Updated to use method structuredType.Properties().
In reply to: 213509115 [](ancestors = 213509115,209788492,198059137)
/// </summary> | ||
/// <param name="edmStructuredType">The EDM structural type.</param> | ||
/// <returns>All the properties of this type.</returns> | ||
private static IEnumerable<IEdmProperty> GetSelfAndBaseTypeProperties(IEdmStructuredType edmStructuredType) |
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.
GetSelfAndBaseTypeProperties [](start = 49, length = 28)
Note that you can use the Properties() extension method to get all declared and inherited properties for a structured type. #Closed
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.
@mikepizzo I have made one more update for the optimization we discussed in office yesterday. Thanks for the suggestion. #Closed |
WriterValidationUtils.ValidatePropertyDefined(this.currentPropertyInfo, this.MessageWriterSettings.ThrowOnUndeclaredPropertyForNonOpenType); | ||
|
||
duplicatePropertyNameChecker.ValidatePropertyUniqueness(property); | ||
|
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.
Not sure why these were moved? Can we put the two checks back before line 206 (before we write the type annotation for undeclared properties)? #Closed
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.
IDuplicatePropertyNameChecker duplicatePropertyNameChecker) | ||
{ | ||
WriterValidationUtils.ValidatePropertyNotNull(property); | ||
|
||
ODataValue value = property.ODataValue; | ||
|
||
string propertyName = property.Name; |
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.
To reduce unnecessary changes, can we move this back to after writing the instance annotation on line 213? #Closed
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.
} | ||
|
||
return dynamicProperties; | ||
} |
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.
These two methods should be deleted if they are no longer used.
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 up GetSelectedDynamicProperties.
GetSelectedProperties is still used by the test w/o using resource state.
In reply to: 233187531 [](ancestors = 233187531)
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 don't like having methods (even internal) that are only used by tests. Is there another way the test can get the selected properties it is validating?
In reply to: 233297316 [](ancestors = 233297316,233187531)
} | ||
|
||
/// <summary> | ||
/// Gets the list of names for selected declared or dynamic properties having values of null. |
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.
Gets the list of names for selected declared or dynamic properties having values of null. [](start = 11, length = 90)
Does this return the names of the properties having null values, or the names of (nullable) properties missing from the payload? #Closed
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.
Updated. Should be the names of properties missing from payload, which will be restored to null.
In reply to: 233188777 [](ancestors = 233188777)
/// <param name="structuredType">The current structured type.</param> | ||
/// <param name="resourceState">The current resource state.</param> | ||
/// <returns>The list of property names with null values.</returns> | ||
internal IList<string> GetSelectedNullValueProperties(IEdmStructuredType structuredType, |
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.
GetSelectedNullValueProperties [](start = 31, length = 30)
We are doing a lot of the same work (i.e., determining the set of nullable properties) for each resource in the result. I wonder if it wouldn't be more efficient to determine the set of nullable property names up front, and then, at the end of writing each resource, restore nulls for the properties from that static list that have not been read.
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.
The current logic follows the overall design of ODataJsonLightReader's EndEntry() method and has been the design from the beginning. It restores all missing nulls for the current entry all at once when the entry is deserialized. If we restored at the end of writing each resource, it would still need to perform the same restoration of null values, and with additional cost of caching information for null properties. Therefore doing it in flight is more efficient.
In reply to: 233190829 [](ancestors = 233190829)
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 understand that we are restoring null values at the end -- that's fine. What I am questioning is whether we have to identify the set of nullable properties each time, or if we can do that once for the result (i.e., could the SelectPropertiesNode cache the set of names of nullable properties, and then when we restore we can just compare the set of properties written to the set of nullable properties and restore any missing, rather than recalculating the set of nullable properties each time?
In reply to: 233299115 [](ancestors = 233299115,233190829)
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.
Since SelectedPropertiesNode is designed as part of context url parsing and is used to represent relatively static context url information, I am not sure it is proper for SelectedPropertiesNode to cache null properties, which is related to runtime payload and is not intrinsic part of SelectedPropertiesNode.
Additionally, there are no 'recalculating' per se; the calculations of set of omitted null properties are required for each structured property or entry on different level regardless whether restoration is done in-flight or via caching, and restoring it in-flight can save the cost of caching and fits with the design of the reader.
In reply to: 233553959 [](ancestors = 233553959,233299115,233190829)
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.
Again, I'm interested in whether we can avoid determining the set of nullable properties in the SelectedPropertiesNode each time, not evaluating what the set of actual null property values is for each resource.
In reply to: 233666008 [](ancestors = 233666008,233553959,233299115,233190829)
I like the new code much better. See my comments inline:
In reply to: 436729775 [](ancestors = 436729775) |
As per separate thread, based on clarification from OASIS, we should not omit annotated values that are null, and should not restore missing values for annotated properties. In reply to: 436424268 [](ancestors = 436424268,435924987) |
The changes related to excluding annotated values is in a forked branch I have shared with you. Will be integrated into this PR, pending on double-checking with detailed requirements as we discussed. In reply to: 438406870 [](ancestors = 438406870,436424268,435924987) |
@mikepizzo Please see the latest updates per your comments today. Currently there is one issue pending related to the annotated properties skipping omit-nulls, which will be merged into this PR when the detailed requirement is finalized. Thanks. #Closed |
… tests in PropertyAndValueJsonLightReaderIntegrationTests.cs
@mikepizzo I have updated the names to 'nestedResourceInfo'. They are from some original test cases that are not changed by this PR but it makes sense to update them. I have also provided my thoughts on your question related to caching null properties during restoration. Thanks. #Closed |
I don't see fd97a44 as part of this PR. It looks like biaol-odata@fd97a44 handles not omitting writing the null value for annotated properties, but we need to validate handling of reading an annotated property that has no value. Can ODL read/write a property with annotations and no value? Is Value=null handled the same as Value=ODataNull? Seems like they should be handled differently (the later former should write the annotations, if any, but no value). This could be a breaking change, however, if this is not how it's handled currently. In reply to: 438516788 [](ancestors = 438516788,438406870,436424268,435924987) |
@mikepizzo @biaol-odata @xuzhg |
f9519fd
to
14a4b12
Compare
@mikepizzo @biaol-odata @xuzhg |
Issues
This pull request introduces omitNullValues controlled by omit-values=nulls Preference header per OData v4.01.
Description
Based on PR #944 from kosinsky.
Checklist (Uncheck if it is not completed)
Additional work necessary
If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.