diff --git a/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs b/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs index caee4b169c..cbe63d1aa3 100644 --- a/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs +++ b/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs @@ -81,6 +81,64 @@ internal BufferingJsonReader(IJsonReader innerReader, string inStreamErrorProper this.currentBufferedNode = null; } + internal bool TryGetValueFromBuffered(string propertyName, bool removeIfExist, out object value) + { + bool found = false; + BufferedNode currentNode = this.bufferedNodesHead; + if (this.bufferedNodesHead != null) + { + do + { + if (currentNode.NodeType == JsonNodeType.StartObject || + currentNode.NodeType == JsonNodeType.StartArray) + { + break; + } + + if (currentNode.NodeType == JsonNodeType.Property && + currentNode.Value.ToString() == propertyName && + currentNode.Next != currentNode) // must have another node + { + found = true; + break; + } + + currentNode = currentNode.Next; + } + while (currentNode != this.bufferedNodesHead); + } + + value = null; + if (!found) + { + return false; + } + + BufferedNode valueNode = currentNode.Next; + + if (removeIfExist) + { + currentNode.Previous.Next = valueNode.Next; + valueNode.Next.Previous = currentNode.Previous; + + if (this.bufferedNodesHead == currentNode) + { + this.bufferedNodesHead = valueNode.Next; + } + + if (this.isBuffering) + { + if (this.currentBufferedNode == currentNode || this.currentBufferedNode == valueNode) + { + this.currentBufferedNode = this.bufferedNodesHead; + } + } + } + + value = valueNode.Value; + return true; + } + /// /// The type of the last node read. /// diff --git a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs index 5342885a9f..97e0ea73f5 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonResourceDeserializer.cs @@ -100,6 +100,14 @@ internal void ReadResourceTypeName(IODataJsonReaderResourceState resourceState) Debug.Assert(resourceState != null, "resourceState != null"); this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + object value; + if (this.JsonReader.TryGetValueFromBuffered(ODataJsonConstants.PrefixedODataTypePropertyName, removeIfExist: true, out value) || + this.JsonReader.TryGetValueFromBuffered(ODataJsonConstants.SimplifiedODataTypePropertyName, removeIfExist: true, out value)) + { + resourceState.Resource.TypeName = ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName(value as string)); + return; + } + // If the current node is the odata.type property - read it. if (this.JsonReader.NodeType == JsonNodeType.Property) { @@ -821,10 +829,16 @@ internal object ReadEntryInstanceAnnotation(string annotationName, bool anyPrope return this.ReadAndValidateAnnotationStringValue(annotationName); case ODataAnnotationNames.ODataRemoved: // 'odata.removed' + // If the value of 'odata.removed' is an object, let's throw exception since it should be not read here. + if (this.JsonReader.NodeType == JsonNodeType.StartObject) { throw new ODataException(ODataErrorStrings.ODataJsonResourceDeserializer_UnexpectedDeletedEntryInResponsePayload); } + // for others, let's skip it + this.JsonReader.SkipValue(); + return null; + default: ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); Debug.Assert( @@ -905,6 +919,9 @@ internal void ApplyEntryInstanceAnnotation(IODataJsonReaderResourceState resourc mediaResource.ETag = (string)annotationValue; break; + case ODataAnnotationNames.ODataRemoved: // 'odata.removed' + break; + default: ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); Debug.Assert( diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs index 60f2c06391..80503f882f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs @@ -1358,6 +1358,159 @@ private static IEdmModel GetModelWithAbstractType(out EdmEntitySet entitySet) return model; } + [Fact] + public void ReadingResourceWithInstanceAnnotationAndODataTypeWorks1() + { + const string payload = + "{\"@odata.context\":\"http://svc/$metadata#EntitySet/$entity\"," + + "\"@odata.type\":\"#Namespace.EntityType\"," + + "\"Name\":\"SampleName\"," + + "\"@removed\":false," + + "\"ID\":89}"; + + EdmEntitySet entitySet = EntitySet; + IEdmModel model = Model; + IEdmEntityType entityType = EntitySet.EntityType; + + InMemoryMessage message = new InMemoryMessage(); + message.SetHeader("Content-Type", "application/json;odata.metadata=minimal"); + message.Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); + + ODataResource topLevelResource = null; + ODataMessageReaderSettings settings = new ODataMessageReaderSettings(ODataVersion.V401) + { + }; + + using (var messageReader = new ODataMessageReader((IODataResponseMessage)message, settings, model)) + { + var reader = messageReader.CreateODataResourceReader(entitySet, entityType); + while (reader.Read()) + { + switch (reader.State) + { + case ODataReaderState.ResourceEnd: + topLevelResource = (ODataResource)reader.Item; + break; + } + } + } + + Assert.NotNull(topLevelResource); + Assert.Equal(new Uri("http://svc/EntitySet(89)"), topLevelResource.Id); + + Assert.Equal("Namespace.EntityType", topLevelResource.TypeName); + Assert.Equal("SampleName", Assert.IsType(topLevelResource.Properties.First(p => p.Name == "Name")).Value); + Assert.Equal(89, Assert.IsType(topLevelResource.Properties.First(p => p.Name == "ID")).Value); + } + + [Fact] + public void ReadingResourceWithInstanceAnnotationAndODataTypeWorks() + { + const string payload = + "{\"@odata.context\":\"http://svc/$metadata#EntitySet/$entity\"," + + "\"@odata.type\":\"#Namespace.EntityType\"," + + "\"Name\":\"SampleName\"," + + "\"@NS.removed\":false," + + "\"ID\":89}"; + + EdmEntitySet entitySet = EntitySet; + IEdmModel model = Model; + IEdmEntityType entityType = EntitySet.EntityType; + + InMemoryMessage message = new InMemoryMessage(); + message.SetHeader("Content-Type", "application/json;odata.metadata=minimal"); + message.Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); + + ODataResource topLevelResource = null; + ODataMessageReaderSettings settings = new ODataMessageReaderSettings(ODataVersion.V401) + { + ShouldIncludeAnnotation = (annotation) => true + }; + + using (var messageReader = new ODataMessageReader((IODataResponseMessage)message, settings, model)) + { + var reader = messageReader.CreateODataResourceReader(entitySet, entityType); + while (reader.Read()) + { + switch (reader.State) + { + case ODataReaderState.ResourceEnd: + topLevelResource = (ODataResource)reader.Item; + break; + } + } + } + + Assert.NotNull(topLevelResource); + Assert.Equal(new Uri("http://svc/EntitySet(89)"), topLevelResource.Id); + + Assert.Equal("Namespace.EntityType", topLevelResource.TypeName); + Assert.Equal("SampleName", Assert.IsType(topLevelResource.Properties.First(p => p.Name == "Name")).Value); + Assert.Equal(89, Assert.IsType(topLevelResource.Properties.First(p => p.Name == "ID")).Value); + + ODataInstanceAnnotation annotation = Assert.Single(topLevelResource.InstanceAnnotations); + Assert.Equal("NS.removed", annotation.Name); + Assert.Equal(false, annotation.Value.FromODataValue()); + } + + [Fact] + public void ReadingDeletedResourceContainsODataTypeWorks() + { + const string payload = +"{\"@odata.context\":\"http://svc/$metadata#EntitySet/$delta\"," + + "\"value\":[" + + "{" + + "\"@odata.type\":\"#Namespace.EntityType\"," + + "\"Name\":\"SampleName\"," + + "\"@removed\":{\"reason\":\"deleted\"}," + + "\"ID\":89" + + "}" + + "]" + +"}"; + + EdmEntitySet entitySet = EntitySet; + IEdmModel model = Model; + IEdmEntityType entityType = EntitySet.EntityType; + + InMemoryMessage message = new InMemoryMessage(); + message.SetHeader("Content-Type", "application/json;odata.metadata=minimal"); + message.Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)); + + ODataMessageReaderSettings settings = new ODataMessageReaderSettings(ODataVersion.V401); + + ODataDeletedResource deletedResource = null; + using (var messageReader = new ODataMessageReader((IODataResponseMessage)message, settings, model)) + { + var reader = messageReader.CreateODataDeltaResourceSetReader(entitySet, entityType); + while (reader.Read()) + { + switch (reader.State) + { + case ODataReaderState.DeltaResourceSetStart: + break; + + case ODataReaderState.DeltaResourceSetEnd: + break; + + case ODataReaderState.DeletedResourceStart: + deletedResource = (ODataDeletedResource)reader.Item; + break; + case ODataReaderState.DeletedResourceEnd: + break; + } + } + } + + Assert.NotNull(deletedResource); + Assert.Equal(new Uri("http://svc/EntitySet(89)"), deletedResource.Id); + + Assert.Equal("Namespace.EntityType", deletedResource.TypeName); + + Assert.Equal(2, deletedResource.Properties.Count()); + Assert.Equal("SampleName", Assert.IsType(deletedResource.Properties.First(p => p.Name == "Name")).Value); + Assert.Equal(89, Assert.IsType(deletedResource.Properties.First(p => p.Name == "ID")).Value); + } + [Theory] [InlineData("minimal")] [InlineData("full")] diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ReorderingJsonReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ReorderingJsonReaderTests.cs index d0da89255d..a743e73522 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ReorderingJsonReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/ReorderingJsonReaderTests.cs @@ -307,6 +307,7 @@ await SetupReorderingJsonReaderAndRunTestAsync( await reorderingReader.ReadPrimitiveValueAsync()); }); } + [Fact] public async Task ReadReorderedPayloadContainingSimplifiedODataAnnotationsAsync() { @@ -345,6 +346,86 @@ await SetupReorderingJsonReaderAndRunTestAsync( }); } + [Fact] + public async Task ReadReorderedPayload_OrderingContextRemovedTypeIdAsync() + { + var payload = "{" + + "\"Id\":1," + + "\"@odata.id\":\"http://tempuri.org/Customers(1)\"," + + "\"Name\":\"Sue\"," + + "\"@odata.removed\":{\"reason\":\"deleted\"}," + + "\"@odata.type\":\"SomeEntityType\"," + + "\"@odata.context\":\"any\"" + + "}"; + + await SetupReorderingJsonReaderAndRunTestAsync( + payload, + async (reorderingReader) => + { + // 1. should be '@odata.context' + Assert.Equal("@odata.context", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip context value + + // 2. should be '@odata.removed' + Assert.Equal("@odata.removed", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + // 3. should be '@odata.type' + Assert.Equal("@odata.type", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + // 4. should be '@odata.id' + Assert.Equal("@odata.id", await reorderingReader.ReadPropertyNameAsync()); + Assert.Equal("http://tempuri.org/Customers(1)", await reorderingReader.ReadPrimitiveValueAsync()); + + Assert.Equal("Id", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + Assert.Equal("Name", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + }); + } + + [Fact] + public async Task ReadReorderedPayload_OrderingContextRemovedTypeId_ContainingSimplifiedAnnotationAsync() + { + var payload = "{" + + "\"Id\":1," + + "\"@id\":\"http://tempuri.org/Customers(1)\"," + + "\"Name\":\"Sue\"," + + "\"@removed\":{\"reason\":\"deleted\"}," + + "\"@context\":\"any\"," + + "\"@type\":\"SomeEntityType\""+ + "}"; + + await SetupReorderingJsonReaderAndRunTestAsync( + payload, + async (reorderingReader) => + { + // 1. should be '@context' + Assert.Equal("@context", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip context value + + // 2. should be '@removed' + Assert.Equal("@removed", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + // 3. should be '@type' + Assert.Equal("@type", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + // 4. should be '@id' + Assert.Equal("@id", await reorderingReader.ReadPropertyNameAsync()); + Assert.Equal("http://tempuri.org/Customers(1)", await reorderingReader.ReadPrimitiveValueAsync()); + + Assert.Equal("Id", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + + Assert.Equal("Name", await reorderingReader.ReadPropertyNameAsync()); + await reorderingReader.SkipValueAsync(); // Skip object value + }); + } + [Fact] public async Task ReadBinaryValueAsync() {