From 3337a09755ee0951da7ad21b58919ef0fa07a227 Mon Sep 17 00:00:00 2001 From: John Gathogo Date: Fri, 6 Sep 2024 12:10:49 +0300 Subject: [PATCH] Fix subtle bug when deserializing property without value --- .../ODataResourceDeserializer.cs | 8 ++++- .../Wrapper/ODataReaderExtensions.cs | 4 +++ .../ODataResourceDeserializerTests.cs | 34 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs index bbe7a6b5f..68371295f 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -489,8 +489,14 @@ public virtual void ApplyStructuralProperties(object resource, ODataResourceWrap throw new ArgumentNullException(nameof(resourceWrapper)); } - foreach (ODataProperty property in resourceWrapper.Resource.Properties) + foreach (ODataPropertyInfo propertyInfo in resourceWrapper.Resource.Properties) { + if (!(propertyInfo is ODataProperty property)) + { + // Cannot deserialize property without value + continue; + } + ApplyStructuralProperty(resource, property, structuredType, readContext); } } diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs index cc9a6346e..6076ebc26 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Wrapper/ODataReaderExtensions.cs @@ -200,6 +200,10 @@ private static void ReadODataItem(ODataReader reader, Stack it resourceSetParentWrapper.Items.Add(new ODataPrimitiveWrapper((ODataPrimitiveValue)reader.Item)); break; + case ODataReaderState.NestedProperty: + // Property without value - do nothing + break; + default: Contract.Assert(false, "We should never get here, it means the ODataReader reported a wrong state."); break; diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs index 8edab1348..5b52c951f 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs @@ -1553,6 +1553,40 @@ public void ReadAsync_ThrowsOnUnknownEntityType() typeof(Product), _readContext).Wait(), "The property 'Concurrency' does not exist on type 'ODataDemo.Product'. Make sure to only use property names that are defined by the type or mark the type as open type."); } + [Fact] + public async Task ReadAsync_ForPropertyWithoutValue() + { + // Arrange + string content = "{" + + "\"ID\":0," + + "\"Name\":\"Bread\"," + + "\"Description\":\"Whole grain bread\"," + + "\"PublishDate\":\"1997-07-01\"," + + "\"ReleaseDate@odata.type\":\"#Edm.DateTimeOffset\"," + // OData annotation - Property without value + "\"DiscontinuedDate@Is.DateTimeOffset\":true," + // Custom annotation - Property without value + "\"Rating\":4," + + "\"Price\":2.5" + + "}"; + + ODataResourceDeserializer deserializer = new ODataResourceDeserializer(_deserializerProvider); + + // Act + Product product = await deserializer.ReadAsync( + GetODataMessageReader(content, _edmModel), + typeof(Product), + _readContext) as Product; + + // Assert + Assert.Equal(0, product.ID); + Assert.Equal(4, product.Rating); + Assert.Equal(2.5m, product.Price); + Assert.Equal(product.PublishDate, new Date(1997, 7, 1)); + Assert.Equal("Bread", product.Name); + Assert.Equal("Whole grain bread", product.Description); + Assert.Null(product.ReleaseDate); + Assert.Null(product.DiscontinuedDate); + } + private static ODataMessageReader GetODataMessageReader(IODataRequestMessage oDataRequestMessage, IEdmModel edmModel) { return new ODataMessageReader(oDataRequestMessage, new ODataMessageReaderSettings(), edmModel);