Skip to content
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

Draft
wants to merge 16 commits into
base: release-7.x
Choose a base branch
from

Conversation

biaol-odata
Copy link
Contributor

Issues

This pull request introduces omitNullValues controlled by omit-values=nulls Preference header per OData v4.01.

Description

  • Current changes include the following areas: *
  • serialize JSON object to response with omit null values option according to Preference-Applied header's parameter value.
  • de-serialize JSON object from response with omitted null values restored per the option used according to the Preference-Applied header's parameter value.
  • has no effects on request payload and delta payload.

Based on PR #944 from kosinsky.

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

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.

@biaol-odata biaol-odata changed the title Pr omit values null Omit null property value in JSON response Mar 6, 2018
@biaol-odata biaol-odata requested a review from mikepizzo March 6, 2018 20:36
/// <summary>
/// True if null values are omitted, per the omit-values parameter in Preference header of the message.
/// </summary>
internal bool NullValuesOmitted { get; set; }
Copy link
Contributor

@kosinsky kosinsky Mar 6, 2018

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

Copy link
Contributor Author

@biaol-odata biaol-odata Mar 6, 2018

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;
Copy link
Contributor

@kosinsky kosinsky Mar 6, 2018

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

Copy link
Contributor Author

@biaol-odata biaol-odata Mar 12, 2018

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

@kosinsky
Copy link
Contributor

kosinsky commented Mar 6, 2018

        if (this.JsonLightOutputContext.MessageWriterSettings.Validations != ValidationKinds.None)

For performance reasons consider adding following optimization:

        ODataValue value = property.ODataValue;

        // If no validation is required, we don't need property serialization info and could try to write null property right away
        // For very wide and sparse outputs it allows to avoid a lot of dictionary lookups
        if (!this.MessageWriterSettings.ThrowIfTypeConflictsWithMetadata && omitNullValues)
        {
            if (value is ODataNullValue || value == null)
            {
                return;
            }
        } #Closed

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;
Copy link
Contributor Author

@biaol-odata biaol-odata Mar 7, 2018

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

Copy link
Member

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)

Copy link
Contributor Author

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()
Copy link
Contributor Author

@biaol-odata biaol-odata Mar 7, 2018

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

Copy link
Contributor Author

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()
Copy link
Contributor Author

@biaol-odata biaol-odata Mar 7, 2018

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.

Copy link
Contributor Author

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)

Copy link
Member

@mikepizzo mikepizzo Nov 27, 2018

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))
Copy link
Contributor Author

@biaol-odata biaol-odata Mar 7, 2018

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

Copy link
Contributor Author

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)

Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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)

Copy link
Contributor Author

@biaol-odata biaol-odata May 10, 2018

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)

kosinsky
kosinsky previously approved these changes Mar 13, 2018
Copy link
Contributor

@kosinsky kosinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@@ -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.
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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);
}
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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.
}
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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>();
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

@biaol-odata biaol-odata May 9, 2018

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)
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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);
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved. Thanks.


In reply to: 186558582 [](ancestors = 186558582)

{
this.WriteNullProperty(property);
this.WriteNullProperty(property, omitNullValues);
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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)

Copy link
Member

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a nested if-clause inside L219, code won't return when the if-clause @L221 is not satisfied.


In reply to: 198078564 [](ancestors = 198078564,187209557,186561856)

Copy link
Member

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:

  1. If this is top level, in which case omitNullValues doesn't apply (it's already ignored in WriteNullProperty)
  2. 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)

Copy link
Contributor Author

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)

Copy link
Member

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)

Copy link
Contributor Author

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)
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

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)

Copy link
Member

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)

Copy link
Contributor Author

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)

Copy link
Member

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)

Copy link
Contributor Author

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)
Copy link
Member

@mikepizzo mikepizzo May 7, 2018

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

Copy link
Contributor Author

@biaol-odata biaol-odata May 10, 2018

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)

@biaol-odata biaol-odata force-pushed the pr-omitValuesNull branch 2 times, most recently from 954c798 to 88fa5ef Compare May 14, 2018 17:24
@biaol-odata
Copy link
Contributor Author

biaol-odata commented May 14, 2018

@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

@biaol-odata
Copy link
Contributor Author

biaol-odata commented Jun 1, 2018

@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;
Copy link
Member

@mikepizzo mikepizzo Jun 26, 2018

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

Copy link
Contributor Author

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)

Copy link
Member

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)

Copy link
Contributor Author

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)

Copy link
Member

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;
Copy link
Member

@mikepizzo mikepizzo Jun 26, 2018

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by design. For current level only.


In reply to: 198059137 [](ancestors = 198059137)

Copy link
Member

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)

Copy link
Contributor Author

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)
Copy link
Member

@mikepizzo mikepizzo Jun 26, 2018

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Thanks.


In reply to: 198061988 [](ancestors = 198061988)

@biaol-odata
Copy link
Contributor Author

biaol-odata commented Nov 7, 2018

@mikepizzo I have made one more update for the optimization we discussed in office yesterday. Thanks for the suggestion. #Closed

@biaol-odata biaol-odata removed the Ready for review Use this label if a pull request is ready to be reviewed label Nov 8, 2018
@biaol-odata biaol-odata added the Ready for review Use this label if a pull request is ready to be reviewed label Nov 8, 2018
WriterValidationUtils.ValidatePropertyDefined(this.currentPropertyInfo, this.MessageWriterSettings.ThrowOnUndeclaredPropertyForNonOpenType);

duplicatePropertyNameChecker.ValidatePropertyUniqueness(property);

Copy link
Member

@mikepizzo mikepizzo Nov 13, 2018

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved back to before L206. Thanks.


In reply to: 233183156 [](ancestors = 233183156)

IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
{
WriterValidationUtils.ValidatePropertyNotNull(property);

ODataValue value = property.ODataValue;

string propertyName = property.Name;
Copy link
Member

@mikepizzo mikepizzo Nov 13, 2018

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. done.


In reply to: 233183812 [](ancestors = 233183812)

}

return dynamicProperties;
}
Copy link
Member

@mikepizzo mikepizzo Nov 13, 2018

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.

Copy link
Contributor Author

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)

Copy link
Member

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.
Copy link
Member

@mikepizzo mikepizzo Nov 13, 2018

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

Copy link
Contributor Author

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,
Copy link
Member

@mikepizzo mikepizzo Nov 13, 2018

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.

Copy link
Contributor Author

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)

Copy link
Member

@mikepizzo mikepizzo Nov 14, 2018

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)

Copy link
Contributor Author

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)

Copy link
Member

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)

@mikepizzo
Copy link
Member

I like the new code much better. See my comments inline:

  1. it looks like the old methods, that separately read declared and dynamic, are still there and should be removed?
  2. rather than determine the list of nullable properties each time we read a resource, can we do it once for the result, and then compare what we've read with that static list of nullable properties?

In reply to: 436729775 [](ancestors = 436729775)

@mikepizzo
Copy link
Member

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)

@biaol-odata
Copy link
Contributor Author

biaol-odata commented Nov 14, 2018

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.
Edit:
Actually the change related to excluding annotated values has been integrated into this PR as commit fd97a44. Pending on finalized requirement.


In reply to: 438406870 [](ancestors = 438406870,436424268,435924987)

@biaol-odata
Copy link
Contributor Author

biaol-odata commented Nov 14, 2018

@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
@biaol-odata
Copy link
Contributor Author

biaol-odata commented Nov 15, 2018

@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

@mikepizzo
Copy link
Member

mikepizzo commented Nov 27, 2018

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)

@raheph raheph assigned mikepizzo and unassigned biaol-odata Dec 19, 2018
@mikepizzo mikepizzo removed the Ready for review Use this label if a pull request is ready to be reviewed label Apr 29, 2019
@agrabhi
Copy link

agrabhi commented Sep 16, 2019

@mikepizzo @biaol-odata @xuzhg
we need above feature for one of above api, because many properties tend to be null there. It seems like this PR has not been touched for a while. What can be done to get this PR checked in

@xuzhg xuzhg force-pushed the master branch 2 times, most recently from f9519fd to 14a4b12 Compare September 22, 2019 21:12
@agrabhi
Copy link

agrabhi commented Oct 27, 2019

@mikepizzo @biaol-odata @xuzhg
Is this PR on radar for checkin soon?

@mikepizzo mikepizzo marked this pull request as draft March 28, 2023 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants