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()
{