diff --git a/src/Microsoft.OData.Core/ODataWriterCore.cs b/src/Microsoft.OData.Core/ODataWriterCore.cs index 9a0031e3a6..c68e810c07 100644 --- a/src/Microsoft.OData.Core/ODataWriterCore.cs +++ b/src/Microsoft.OData.Core/ODataWriterCore.cs @@ -2932,7 +2932,7 @@ private void PromoteNestedResourceInfoScope(ODataItem content) NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent); this.scopeStack.Push(newScope); - if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) + if ((newScope.ItemType == null || newScope.ItemType.IsUntyped()) && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) { ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue; if (primitiveValue != null) @@ -2941,10 +2941,18 @@ private void PromoteNestedResourceInfoScope(ODataItem content) } else { - ODataResourceBase resource = content as ODataResourceBase; - newScope.ResourceType = resource != null - ? GetResourceType(resource) - : GetResourceSetType(content as ODataResourceSetBase); + if (content is ODataResourceBase resource) + { + newScope.ResourceType = string.IsNullOrEmpty(resource.TypeName) ? + EdmUntypedStructuredType.Instance : + GetResourceType(resource); + } + else if (content is ODataResourceSetBase resourceSet) + { + newScope.ResourceType = string.IsNullOrEmpty(resourceSet.TypeName) ? + EdmUntypedStructuredType.Instance : + GetResourceSetType(resourceSet); + } } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs index 43a27d0b57..cc50cdde31 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs @@ -196,6 +196,45 @@ public void WriteResourceDeclaredCollectionUntypedProperty_WorksForCollectionVal Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[\"abc\",null,42]}", result); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WriteResourceCollectionUntypedProperty_WorksResourceInNestedCollectionOfCollection2(bool isOpen) + { + string typeName = isOpen ? "Server.NS.ServerOpenEntityType" : "Server.NS.ServerEntityType"; + EdmEntitySet entitySet = isOpen ? this.serverOpenEntitySet : this.serverEntitySet; + EdmEntityType entityType = isOpen ? this.serverOpenEntityType : this.serverEntityType; + string propertyName = isOpen ? "AnyDynamic" : "Infos"; + + string actual = WriteEntryPayload(entitySet, entityType, + writer => + { + writer.WriteStart(new ODataResource { TypeName = typeName }); + writer.WriteStart(new ODataNestedResourceInfo { Name = propertyName, IsCollection = true }); + writer.WriteStart(new ODataResourceSet { TypeName = "Collection(Edm.Untyped)" }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(new ODataResource + { + TypeName = "Edm.Untyped", + Properties = new ODataProperty[] + { + new ODataProperty { Name = "FirstName", Value = "Kerry"} + } + }); + writer.WriteEnd(); // End of "Edm.Untyped" + writer.WriteEnd(); + writer.WriteEnd(); // End of "Infos" / AnyDynamic + writer.WriteEnd(); + writer.WriteEnd(); + }); + + string result = isOpen ? + "{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverOpenEntitySet/$entity\",\"AnyDynamic\":[[{\"FirstName\":\"Kerry\"}]]}" : + "{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[[{\"FirstName\":\"Kerry\"}]]}"; + + Assert.Equal(result, actual); + } + private string WriteDeclaredUntypedProperty(ODataProperty untypedProperty) { var entry = new ODataResource