diff --git a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs index 36be01404a..1d0291ff49 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs @@ -2427,7 +2427,7 @@ await this.JsonReader.ReadAsync() /// /// This method Creates an ODataDeltaDeletedEntry and fills in the Id and Reason properties, if specified in the payload. /// - internal async Task ReadDeletedResourceAsync() + internal async ValueTask ReadDeletedResourceAsync() { this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); @@ -2524,7 +2524,7 @@ await this.JsonReader.ReadAsync() /// /// This method Creates an ODataDeltaDeletedEntry and fills in the Id and Reason properties, if specified in the payload. /// - internal async Task ReadDeletedEntryAsync() + internal async ValueTask ReadDeletedEntryAsync() { this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); Uri id = null; @@ -2594,27 +2594,44 @@ await this.JsonReader.ReadAsync() /// /// This method fills the ODataDelta(Deleted)Link.Source property if the id is found in the payload. /// - internal async Task ReadDeltaLinkSourceAsync(ODataDeltaLinkBase link) + internal ValueTask ReadDeltaLinkSourceAsync(ODataDeltaLinkBase link) { this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - // If the current node is the source property - read it. - if (this.JsonReader.NodeType == JsonNodeType.Property && - string.Equals(ODataJsonConstants.ODataSourcePropertyName, await this.JsonReader.GetPropertyNameAsync().ConfigureAwait(false), StringComparison.Ordinal)) + if (this.JsonReader.NodeType != JsonNodeType.Property) { - Debug.Assert(link.Source == null, "source should not have already been set"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return ValueTask.CompletedTask; + } + + return AwaitReadDeltaLinkSourceAsync(this, link); + + static async ValueTask AwaitReadDeltaLinkSourceAsync( + ODataJsonResourceDeserializer paramThis, + ODataDeltaLinkBase paramLink + ) + { + string propertyName = await paramThis.JsonReader.GetPropertyNameAsync().ConfigureAwait(false); + + if (!string.Equals(ODataJsonConstants.ODataSourcePropertyName, propertyName, StringComparison.Ordinal)) + { + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return; + } + + Debug.Assert(paramLink.Source == null, "source should not have already been set"); // Read over the property to move to its value. - await this.JsonReader.ReadAsync() + await paramThis.JsonReader.ReadAsync() .ConfigureAwait(false); // Read the source value. - link.Source = await this.JsonReader.ReadUriValueAsync() + paramLink.Source = await paramThis.JsonReader.ReadUriValueAsync() .ConfigureAwait(false); - Debug.Assert(link.Source != null, "value for source must be provided"); - } + Debug.Assert(paramLink.Source != null, "value for source must be provided"); - this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } } /// @@ -2630,27 +2647,41 @@ await this.JsonReader.ReadAsync() /// /// This method fills the ODataDelta(Deleted)Link.Relationship property if the id is found in the payload. /// - internal async Task ReadDeltaLinkRelationshipAsync(ODataDeltaLinkBase link) + internal ValueTask ReadDeltaLinkRelationshipAsync(ODataDeltaLinkBase link) { this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - // If the current node is the relationship property - read it. - if (this.JsonReader.NodeType == JsonNodeType.Property && - string.Equals(ODataJsonConstants.ODataRelationshipPropertyName, await this.JsonReader.GetPropertyNameAsync().ConfigureAwait(false), StringComparison.Ordinal)) + if (this.JsonReader.NodeType != JsonNodeType.Property) { - Debug.Assert(link.Relationship == null, "relationship should not have already been set"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return ValueTask.CompletedTask; + } + + return AwaitReadDeltaLinkRelationshipAsync(this, link); + + static async ValueTask AwaitReadDeltaLinkRelationshipAsync( + ODataJsonResourceDeserializer paramThis, + ODataDeltaLinkBase paramLink) + { + string propertyName = await paramThis.JsonReader.GetPropertyNameAsync().ConfigureAwait(false); + + if (!string.Equals(ODataJsonConstants.ODataRelationshipPropertyName, propertyName, StringComparison.Ordinal)) + { + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return; + } + + Debug.Assert(paramLink.Relationship == null, "relationship should not have already been set"); // Read over the property to move to its value. - await this.JsonReader.ReadAsync() - .ConfigureAwait(false); + await paramThis.JsonReader.ReadAsync().ConfigureAwait(false); // Read the relationship value. - link.Relationship = await this.JsonReader.ReadStringValueAsync() - .ConfigureAwait(false); - Debug.Assert(link.Relationship != null, "value for relationship must be provided"); - } + paramLink.Relationship = await paramThis.JsonReader.ReadStringValueAsync().ConfigureAwait(false); + Debug.Assert(paramLink.Relationship != null, "value for relationship must be provided"); - this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } } /// @@ -2666,27 +2697,41 @@ await this.JsonReader.ReadAsync() /// /// This method fills the ODataDelta(Deleted)Link.Target property if the id is found in the payload. /// - internal async Task ReadDeltaLinkTargetAsync(ODataDeltaLinkBase link) + internal ValueTask ReadDeltaLinkTargetAsync(ODataDeltaLinkBase link) { this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - // If the current node is the target property - read it. - if (this.JsonReader.NodeType == JsonNodeType.Property && - string.Equals(ODataJsonConstants.ODataTargetPropertyName, await this.JsonReader.GetPropertyNameAsync().ConfigureAwait(false), StringComparison.Ordinal)) + if (this.JsonReader.NodeType != JsonNodeType.Property) { - Debug.Assert(link.Target == null, "target should not have already been set"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return ValueTask.CompletedTask; + } + + return AwaitReadDeltaLinkTargetAsync(this, link); + + static async ValueTask AwaitReadDeltaLinkTargetAsync( + ODataJsonResourceDeserializer paramThis, + ODataDeltaLinkBase paramLink) + { + string propertyName = await paramThis.JsonReader.GetPropertyNameAsync().ConfigureAwait(false); + + if (!string.Equals(ODataJsonConstants.ODataTargetPropertyName, propertyName, StringComparison.Ordinal)) + { + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return; + } + + Debug.Assert(paramLink.Target == null, "target should not have already been set"); // Read over the property to move to its value. - await this.JsonReader.ReadAsync() - .ConfigureAwait(false); + await paramThis.JsonReader.ReadAsync().ConfigureAwait(false); - // Read the source value. - link.Target = await this.JsonReader.ReadUriValueAsync() - .ConfigureAwait(false); - Debug.Assert(link.Target != null, "value for target must be provided"); - } + // Read the target value. + paramLink.Target = await paramThis.JsonReader.ReadUriValueAsync().ConfigureAwait(false); + Debug.Assert(paramLink.Target != null, "value for target must be provided"); - this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + paramThis.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } } /// @@ -2708,7 +2753,7 @@ await this.JsonReader.ReadAsync() /// JsonNodeType.StartArray Expanded resource set /// JsonNodeType.PrimitiveValue (null) Expanded null /// - internal async Task ReadResourceContentAsync(IODataJsonReaderResourceState resourceState) + internal async ValueTask ReadResourceContentAsync(IODataJsonReaderResourceState resourceState) { Debug.Assert(resourceState != null, "resourceState != null"); Debug.Assert(resourceState.ResourceType != null && this.Model.IsUserModel(), "A non-null resource type and non-null model are required."); @@ -2817,26 +2862,52 @@ readerNestedResourceInfo is ODataJsonReaderNestedInfo || /// A task that represents the asynchronous read operation. /// The value of the TResult parameter contains the annotation value. /// - internal async Task ReadODataOrCustomInstanceAnnotationValueAsync( + internal Task ReadODataOrCustomInstanceAnnotationValueAsync( IODataJsonReaderResourceState resourceState, PropertyParsingResult propertyParsingResult, string annotationName) { - object value = await this.ReadEntryInstanceAnnotationAsync( + Task readEntryInstanceAnnotationTask = this.ReadEntryInstanceAnnotationAsync( annotationName, resourceState.AnyPropertyFound, typeAnnotationFound: true, - propertyAndAnnotationCollector: resourceState.PropertyAndAnnotationCollector).ConfigureAwait(false); - if (propertyParsingResult == PropertyParsingResult.ODataInstanceAnnotation) + propertyAndAnnotationCollector: resourceState.PropertyAndAnnotationCollector); + + if (readEntryInstanceAnnotationTask.IsCompletedSuccessfully) { - resourceState.PropertyAndAnnotationCollector.AddODataScopeAnnotation(annotationName, value); + return Task.FromResult(ProcessInstanceAnnotationValue(resourceState, propertyParsingResult, readEntryInstanceAnnotationTask.Result, annotationName)); } - else + + return AwaitReadODataOrCustomInstanceAnnotationValueAsync(readEntryInstanceAnnotationTask, resourceState, propertyParsingResult, annotationName); + + static object ProcessInstanceAnnotationValue( + IODataJsonReaderResourceState paramResourceState, + PropertyParsingResult paramPropertyParsingResult, + object annotationValue, + string paramAnnotationName) { - resourceState.PropertyAndAnnotationCollector.AddCustomScopeAnnotation(annotationName, value); + if (paramPropertyParsingResult == PropertyParsingResult.ODataInstanceAnnotation) + { + paramResourceState.PropertyAndAnnotationCollector.AddODataScopeAnnotation(paramAnnotationName, annotationValue); + } + else + { + paramResourceState.PropertyAndAnnotationCollector.AddCustomScopeAnnotation(paramAnnotationName, annotationValue); + } + + return annotationValue; + } + + static async Task AwaitReadODataOrCustomInstanceAnnotationValueAsync( + Task readEntryInstanceAnnotationTask, + IODataJsonReaderResourceState paramResourceState, + PropertyParsingResult paramPropertyParsingResult, + string paramAnnotationName) + { + object annotationValue = await readEntryInstanceAnnotationTask.ConfigureAwait(false); + return ProcessInstanceAnnotationValue(paramResourceState, paramPropertyParsingResult, annotationValue, paramAnnotationName); } - return value; } /// @@ -2977,7 +3048,7 @@ await this.ReadMetadataReferencePropertyValueAsync((ODataResourceSet)resourceSet /// The PropertyParsingResult. /// The annotation name. /// A task that represents the asynchronous read operation. - internal async Task ReadODataOrCustomInstanceAnnotationValueAsync( + internal Task ReadODataOrCustomInstanceAnnotationValueAsync( ODataResourceSetBase resourceSet, PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool forResourceSetStart, @@ -2989,7 +3060,24 @@ internal async Task ReadODataOrCustomInstanceAnnotationValueAsync( { // #### annotation 1 #### // built-in "odata." annotation value is added to propertyAndAnnotationCollector then later to resourceSet.InstanceAnnotations. - propertyAndAnnotationCollector.AddODataScopeAnnotation(annotationName, await this.JsonReader.GetValueAsync().ConfigureAwait(false)); + Task annotationTask = this.JsonReader.GetValueAsync(); + + if (annotationTask.IsCompletedSuccessfully) + { + object annotationValue = annotationTask.Result; + try + { + propertyAndAnnotationCollector.AddODataScopeAnnotation(annotationName, annotationValue); + } + catch (ODataException ex) + { + return Task.FromException(ex); + } + } + else + { + return AwaitReadODataOrCustomInstanceAnnotationValueAsync(annotationTask, annotationName, propertyAndAnnotationCollector); + } } // When we are reading the start of a resource set (in scan-ahead mode or not) or when @@ -2999,13 +3087,20 @@ internal async Task ReadODataOrCustomInstanceAnnotationValueAsync( { // #### annotation 2 #### // custom annotation value will be directly added to resourceSet.InstanceAnnotations. - await this.ReadAndApplyResourceSetInstanceAnnotationValueAsync(annotationName, resourceSet, propertyAndAnnotationCollector) - .ConfigureAwait(false); + return this.ReadAndApplyResourceSetInstanceAnnotationValueAsync(annotationName, resourceSet, propertyAndAnnotationCollector); } else { - await this.JsonReader.SkipValueAsync() - .ConfigureAwait(false); + return this.JsonReader.SkipValueAsync(); + } + + static async Task AwaitReadODataOrCustomInstanceAnnotationValueAsync( + Task pendingTask, + string paramAnnotationName, + PropertyAndAnnotationCollector paramPropertyAndAnnotationCollector) + { + object annotationValue = await pendingTask.ConfigureAwait(false); + paramPropertyAndAnnotationCollector.AddODataScopeAnnotation(paramAnnotationName, annotationValue); } } @@ -3261,7 +3356,7 @@ await this.ReadAndValidateAnnotationStringValueAsync(ODataAnnotationNames.ODataT /// Post-Condition: JsonNodeType.EndObject This method doesn't move the reader. /// JsonNodeType.Property /// - internal async Task ReadPropertyWithoutValueAsync(IODataJsonReaderResourceState resourceState, string propertyName) + internal ValueTask ReadPropertyWithoutValueAsync(IODataJsonReaderResourceState resourceState, string propertyName) { Debug.Assert(resourceState != null, "resourceState != null"); Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); @@ -3273,11 +3368,18 @@ internal async Task ReadPropertyWithoutValueAsync(IOD if (edmProperty == null || edmProperty.Type.IsUntyped()) { // Undeclared property - we need to run detection algorithm here. - readerNestedInfo = await this.ReadUndeclaredPropertyAsync(resourceState, propertyName, propertyWithValue: false) - .ConfigureAwait(false); + ValueTask taskReadUndeclaredProperty = this.ReadUndeclaredPropertyAsync(resourceState, propertyName, propertyWithValue: false); this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - return readerNestedInfo; + + if (taskReadUndeclaredProperty.IsCompletedSuccessfully) + { + return taskReadUndeclaredProperty; + } + else + { + return AwaitReadPropertyWithoutValueAsync(taskReadUndeclaredProperty); + } } // Declared property - read it. @@ -3299,7 +3401,9 @@ internal async Task ReadPropertyWithoutValueAsync(IOD if (!readerNestedResourceInfo.HasEntityReferenceLink) { - throw new ODataException(Error.Format(SRResources.ODataJsonResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink, propertyName, ODataAnnotationNames.ODataBind)); + return ValueTask.FromException( + new ODataException(Error.Format(SRResources.ODataJsonResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink, propertyName, ODataAnnotationNames.ODataBind)) + ); } } @@ -3322,7 +3426,13 @@ internal async Task ReadPropertyWithoutValueAsync(IOD } this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - return readerNestedInfo; + return ValueTask.FromResult(readerNestedInfo); + + static async ValueTask AwaitReadPropertyWithoutValueAsync( + ValueTask pendingTask) + { + return await pendingTask.ConfigureAwait(false); + } } /// @@ -3574,7 +3684,7 @@ await this.ReadEntryDataPropertyAsync( /// The value of the TResult parameter contains the for a nested stream property, /// or null if the property shouldn't be streamed. /// - private async Task TryReadAsStreamAsync( + private async ValueTask TryReadAsStreamAsync( IODataJsonReaderResourceState resourceState, IEdmStructuralProperty property, IEdmTypeReference propertyType, @@ -3715,7 +3825,7 @@ private Task ReadOverPropertyNameAsync() /// Post-Condition: JsonNodeType.Property: the next property of the resource /// JsonNodeType.EndObject: the end-object node of the resource /// - private async Task ReadEntryDataPropertyAsync(IODataJsonReaderResourceState resourceState, IEdmProperty edmProperty, string propertyTypeName) + private ValueTask ReadEntryDataPropertyAsync(IODataJsonReaderResourceState resourceState, IEdmProperty edmProperty, string propertyTypeName) { Debug.Assert(resourceState != null, "resourceState != null"); Debug.Assert(edmProperty != null, "edmProperty != null"); @@ -3726,7 +3836,7 @@ private async Task ReadEntryDataPropertyAsync(IODataJsonReaderResourceState reso ODataNullValueBehaviorKind nullValueReadBehaviorKind = this.ReadingResponse ? ODataNullValueBehaviorKind.Default : this.Model.NullValueReadBehaviorKind(edmProperty); - object propertyValue = await this.ReadNonEntityValueAsync( + Task readNonEntityValueTask = this.ReadNonEntityValueAsync( propertyTypeName, edmProperty.Type, propertyAndAnnotationCollector: null, @@ -3734,17 +3844,44 @@ private async Task ReadEntryDataPropertyAsync(IODataJsonReaderResourceState reso validateNullValue: nullValueReadBehaviorKind == ODataNullValueBehaviorKind.Default, isTopLevelPropertyValue: false, insideResourceValue: false, - propertyName: edmProperty.Name).ConfigureAwait(false); + propertyName: edmProperty.Name); - if (nullValueReadBehaviorKind != ODataNullValueBehaviorKind.IgnoreValue || propertyValue != null) + if (readNonEntityValueTask.IsCompletedSuccessfully) { - AddResourceProperty(resourceState, edmProperty.Name, propertyValue); + object propertyValue = readNonEntityValueTask.Result; + InnerAddResourceProperty(this, nullValueReadBehaviorKind, resourceState, edmProperty.Name, propertyValue); + return ValueTask.CompletedTask; } - this.JsonReader.AssertNotBuffering(); - Debug.Assert( - this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, - "Post-Condition: expected JsonNodeType.Property or JsonNodeType.EndObject"); + return AwaitReadNonEntityValueAsync(readNonEntityValueTask, this, nullValueReadBehaviorKind, resourceState, edmProperty.Name); + + static void InnerAddResourceProperty( + ODataJsonResourceDeserializer paramThis, + ODataNullValueBehaviorKind paramNullValueReadBehaviorKind, + IODataJsonReaderResourceState paramResourceState, + string paramPropertyName, + object propertyValue) + { + if (paramNullValueReadBehaviorKind != ODataNullValueBehaviorKind.IgnoreValue || propertyValue != null) + { + AddResourceProperty(paramResourceState, paramPropertyName, propertyValue); + } + paramThis.JsonReader.AssertNotBuffering(); + Debug.Assert( + paramThis.JsonReader.NodeType == JsonNodeType.Property || paramThis.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.Property or JsonNodeType.EndObject"); + } + + static async ValueTask AwaitReadNonEntityValueAsync( + Task readTask, + ODataJsonResourceDeserializer paramThis, + ODataNullValueBehaviorKind paramNullValueReadBehaviorKind, + IODataJsonReaderResourceState paramResourceState, + string paramPropertyName) + { + object propertyValue = await readTask.ConfigureAwait(false); + InnerAddResourceProperty(paramThis, paramNullValueReadBehaviorKind, paramResourceState, paramPropertyName, propertyValue); + } } /// @@ -3764,7 +3901,7 @@ private async Task ReadEntryDataPropertyAsync(IODataJsonReaderResourceState reso /// Post-Condition: JsonNodeType.Property: the next property of the resource /// JsonNodeType.EndObject: the end-object node of the resource /// - private async Task InnerReadUndeclaredPropertyAsync( + private async ValueTask InnerReadUndeclaredPropertyAsync( IODataJsonReaderResourceState resourceState, IEdmStructuredType owningStructuredType, string propertyName, @@ -3898,7 +4035,7 @@ await ValidateExpandedNestedResourceInfoPropertyValueAsync( /// Post-Condition: JsonNodeType.Property: the next property of the resource /// JsonNodeType.EndObject: the end-object node of the resource /// - private async Task ReadUndeclaredPropertyAsync( + private async ValueTask ReadUndeclaredPropertyAsync( IODataJsonReaderResourceState resourceState, string propertyName, bool propertyWithValue) diff --git a/test/UnitTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceDeserializerTests.cs b/test/UnitTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceDeserializerTests.cs index 78c70f1e0f..7fad48ea93 100644 --- a/test/UnitTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceDeserializerTests.cs +++ b/test/UnitTests/Microsoft.OData.Core.Tests/Json/ODataJsonResourceDeserializerTests.cs @@ -1758,7 +1758,7 @@ public async Task ReadDeletedResourceAsync_ThrowsExceptionForMissingODataIdAnnot await AdvanceReaderToFirstPropertyAsync(jsonResourceDeserializer.JsonReader); var exception = await Assert.ThrowsAsync( - () => jsonResourceDeserializer.ReadDeletedResourceAsync()); + () => jsonResourceDeserializer.ReadDeletedResourceAsync().AsTask()); Assert.Equal( SRResources.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties, @@ -1780,7 +1780,7 @@ public async Task ReadDeletedResourceAsync_ThrowsExceptionForODataRemovedAnnotat await AdvanceReaderToFirstPropertyAsync(jsonResourceDeserializer.JsonReader); var exception = await Assert.ThrowsAsync( - () => jsonResourceDeserializer.ReadDeletedResourceAsync()); + () => jsonResourceDeserializer.ReadDeletedResourceAsync().AsTask()); Assert.Equal( Error.Format(SRResources.ODataJsonResourceDeserializer_DeltaRemovedAnnotationMustBeObject, "NaO"), @@ -1804,7 +1804,7 @@ public async Task ReadDeletedEntryAsync_ThrowsExceptionForNestedContent(string p await AdvanceReaderToFirstPropertyAsync(jsonResourceDeserializer.JsonReader); var exception = await Assert.ThrowsAsync( - () => jsonResourceDeserializer.ReadDeletedEntryAsync()); + () => jsonResourceDeserializer.ReadDeletedEntryAsync().AsTask()); Assert.Equal( SRResources.ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry,