diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs index ec85fff2ce..5e40ef2221 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs @@ -1783,6 +1783,7 @@ private object ReadNonEntityValueImplementation( } } + expectedTypeReference = expectedTypeReference != null && expectedTypeReference.IsUntyped() ? null : expectedTypeReference; ODataTypeAnnotation typeAnnotation; EdmTypeKind targetTypeKind; IEdmTypeReference targetTypeReference = this.ReaderValidator.ResolvePayloadTypeNameAndComputeTargetType( @@ -1907,7 +1908,7 @@ private object ReadNonEntityValueImplementation( } // If we have no expected type make sure the collection items are of the same kind and specify the same name. - if (collectionValidator != null) + if (collectionValidator != null && targetTypeKind != EdmTypeKind.None) { string payloadTypeNameFromResult = ODataJsonLightReaderUtils.GetPayloadTypeName(result); Debug.Assert(expectedTypeReference == null, "If a collection validator is specified there must not be an expected value type reference."); diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs index 6d65921811..51a6a8dbcf 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs @@ -1320,6 +1320,7 @@ private ODataJsonLightReaderNestedInfo TryReadAsStream(IODataJsonLightReaderReso Func readAsStream = this.MessageReaderSettings.ReadAsStreamFunc; // is the property a stream or a stream collection, + // untyped collection, // or a binary or binary collection the client wants to read as a stream... if ( (primitiveType != null && @@ -1328,7 +1329,10 @@ private ODataJsonLightReaderNestedInfo TryReadAsStream(IODataJsonLightReaderReso && (property == null || !property.IsKey()) // don't stream key properties && (primitiveType.IsBinary() || primitiveType.IsString() || isCollection)) && readAsStream(primitiveType, isCollection, propertyName, property))) || - ((propertyType == null || propertyType.Definition.AsElementType().IsUntyped()) + (propertyType != null && + isCollection && + propertyType.Definition.AsElementType().IsUntyped()) || + (propertyType == null && (isCollection || this.JsonReader.CanStream()) && readAsStream != null && readAsStream(null, isCollection, propertyName, property))) diff --git a/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs b/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs index b486b47524..215d4c331e 100644 --- a/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs +++ b/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs @@ -1572,7 +1572,7 @@ internal static IEdmTypeReference ToTypeReference(this IEdmType type, bool nulla IEdmStructuredType untypedType = type as IEdmStructuredType; if (untypedType != null) { - return new EdmUntypedStructuredTypeReference(untypedType); + return new EdmUntypedStructuredTypeReference(untypedType, nullable); } return new EdmUntypedTypeReference((IEdmUntypedType)type); diff --git a/src/Microsoft.OData.Core/ODataReaderCore.cs b/src/Microsoft.OData.Core/ODataReaderCore.cs index 078f4e634c..cf9b9c307d 100644 --- a/src/Microsoft.OData.Core/ODataReaderCore.cs +++ b/src/Microsoft.OData.Core/ODataReaderCore.cs @@ -739,7 +739,7 @@ protected void ApplyResourceTypeNameFromPayload(string resourceTypeNameFromPaylo ODataTypeAnnotation typeAnnotation; EdmTypeKind targetTypeKind; IEdmStructuredTypeReference targetResourceTypeReference = - (IEdmStructuredTypeReference)this.inputContext.MessageReaderSettings.Validator.ResolvePayloadTypeNameAndComputeTargetType( + this.inputContext.MessageReaderSettings.Validator.ResolvePayloadTypeNameAndComputeTargetType( EdmTypeKind.None, /*expectStructuredType*/ true, /*defaultPrimitivePayloadType*/ null, @@ -748,7 +748,7 @@ protected void ApplyResourceTypeNameFromPayload(string resourceTypeNameFromPaylo this.inputContext.Model, () => EdmTypeKind.Entity, out targetTypeKind, - out typeAnnotation); + out typeAnnotation) as IEdmStructuredTypeReference; IEdmStructuredType targetResourceType = null; ODataResourceBase resource = this.Item as ODataResourceBase; @@ -766,6 +766,12 @@ protected void ApplyResourceTypeNameFromPayload(string resourceTypeNameFromPaylo { resource.TypeName = resourceTypeNameFromPayload; } + else if (this.CurrentResourceTypeReference.IsUntyped()) + { + targetResourceTypeReference = this.CurrentResourceTypeReference.IsNullable ? + EdmUntypedStructuredTypeReference.NullableTypeReference : + EdmUntypedStructuredTypeReference.NonNullableTypeReference; + } // Set the current resource type since the type might be derived from the expected one. this.CurrentResourceTypeReference = targetResourceTypeReference; diff --git a/src/Microsoft.OData.Core/ReaderValidationUtils.cs b/src/Microsoft.OData.Core/ReaderValidationUtils.cs index 6c22c59ced..6c104d2393 100644 --- a/src/Microsoft.OData.Core/ReaderValidationUtils.cs +++ b/src/Microsoft.OData.Core/ReaderValidationUtils.cs @@ -465,7 +465,7 @@ internal static IEdmTypeReference ResolveAndValidateNonPrimitiveTargetType( payloadTypeKind == EdmTypeKind.TypeDefinition || payloadTypeKind == EdmTypeKind.Untyped, "The payload type kind must be one of None, Primitive, Enum, Untyped, Complex, Entity, Collection or TypeDefinition."); Debug.Assert( - expectedTypeReference == null || expectedTypeReference.TypeKind() == expectedTypeKind, + expectedTypeReference == null || expectedTypeReference.TypeKind() == EdmTypeKind.Untyped || expectedTypeReference.TypeKind() == expectedTypeKind, "The expected type kind must match the expected type reference if that is available."); Debug.Assert( payloadType == null || payloadType.TypeKind == payloadTypeKind, @@ -823,7 +823,10 @@ private static IEdmTypeReference ResolveAndValidateTargetTypeStrictValidationEna { // The payload type must be assignable to the expected type. IEdmTypeReference payloadTypeReference = payloadType.ToTypeReference(/*nullable*/ true); - ValidationUtils.ValidateEntityTypeIsAssignable((IEdmEntityTypeReference)expectedTypeReference, (IEdmEntityTypeReference)payloadTypeReference); + if (!expectedTypeReference.IsUntyped()) + { + ValidationUtils.ValidateEntityTypeIsAssignable((IEdmEntityTypeReference)expectedTypeReference, (IEdmEntityTypeReference)payloadTypeReference); + } // Use the payload type return payloadTypeReference; @@ -1003,7 +1006,7 @@ private static EdmTypeKind ComputeTargetTypeKind( } EdmTypeKind targetTypeKind; - if (expectedTypeKind != EdmTypeKind.None) + if (expectedTypeKind != EdmTypeKind.None && expectedTypeKind != EdmTypeKind.Untyped) { // If we have an expected type, use that. targetTypeKind = expectedTypeKind; diff --git a/src/Microsoft.OData.Core/ValidationUtils.cs b/src/Microsoft.OData.Core/ValidationUtils.cs index 5c4fe418ba..0f0d661533 100644 --- a/src/Microsoft.OData.Core/ValidationUtils.cs +++ b/src/Microsoft.OData.Core/ValidationUtils.cs @@ -400,7 +400,7 @@ internal static void ValidateTypeKind(EdmTypeKind actualTypeKind, EdmTypeKind ex return; } - if (expectedTypeKind != actualTypeKind) + if (expectedTypeKind != EdmTypeKind.Untyped && expectedTypeKind != actualTypeKind) { if (typeName == null) { diff --git a/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs b/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs index 42fc35130b..d147afaec4 100644 --- a/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs +++ b/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs @@ -17,6 +17,11 @@ public sealed class EdmUntypedStructuredType : EdmStructuredType, IEdmStructured private readonly string name; private readonly string fullName; + /// + /// The core Edm.Untyped singleton. + /// + public static readonly EdmUntypedStructuredType Instance = new EdmUntypedStructuredType(); + /// /// Initializes a new instance of the class. /// diff --git a/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs b/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs index 0c9c1e97ca..a5995e07c1 100644 --- a/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs +++ b/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs @@ -13,12 +13,32 @@ namespace Microsoft.OData.Edm /// public class EdmUntypedStructuredTypeReference : EdmTypeReference, IEdmUntypedTypeReference, IEdmStructuredTypeReference { + /// + /// Returns a static instance of a nullable untyped structured type reference. + /// + public static readonly IEdmStructuredTypeReference NullableTypeReference = new EdmUntypedStructuredTypeReference(EdmUntypedStructuredType.Instance, true); + + /// + /// Returns a static instance of a non-nullable untyped structured type reference. + /// + public static readonly IEdmStructuredTypeReference NonNullableTypeReference = new EdmUntypedStructuredTypeReference(EdmUntypedStructuredType.Instance, false); + /// /// Constructor /// /// IEdmStructuredType definition. public EdmUntypedStructuredTypeReference(IEdmStructuredType definition) - : base(definition, true) + : this(definition, true) + { + } + + /// + /// Constructor + /// + /// IEdmStructuredType definition. + /// Denotes whether the type can be nullable. + public EdmUntypedStructuredTypeReference(IEdmStructuredType definition, bool isNullable) + : base(definition, isNullable) { } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs index 857d0736c7..d01bb21432 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs @@ -168,6 +168,8 @@ public void CanReadUntypedCollection() "\"" + binaryValue + "\"" + ",\"" + stringValue + "\"" + ",\"" + stringValue + "\"" + + ", { \"name\": \"Betty\",\"age@type\":\"#Int32\",\"age\": 18}" + + ", { \"@type\": \"#test.customer\",\"name\": \"Betty\",\"age\": 18}" + ",null" + ",true" + ",false" + @@ -186,63 +188,120 @@ public void CanReadUntypedCollection() return true; }; - foreach (Func ShouldStream in new Func[] { StreamCollection, StreamAll }) + Func StreamNone = + (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + { + return false; + }; + + foreach (Func ShouldStream in new Func[] { StreamCollection, StreamAll, StreamNone }) { foreach (Variant variant in GetVariants(ShouldStream)) { int expectedPropertyCount = variant.IsRequest ? 1 : 2; - ODataResource resource = null; - List propertyValues = null; - + List collectionValues = null; + ODataResource currentResource = null; + Stack resources = new Stack(); + ODataPropertyInfo currentProperty = null; + Stack> nestedProperties = new Stack>(); ODataReader reader = CreateODataReader(payload, variant); + while (reader.Read()) { switch (reader.State) { case ODataReaderState.ResourceStart: - if (resource == null) + currentResource = reader.Item as ODataResource; + if (resources.Count == 1) { - resource = reader.Item as ODataResource; + collectionValues.Add(reader.Item); } - else + resources.Push(currentResource); + nestedProperties.Push(new List()); + + break; + + case ODataReaderState.ResourceEnd: + currentResource = resources.Pop(); + List properties = nestedProperties.Pop(); + if (currentResource != null) { - propertyValues.Add(reader.Item); + properties.AddRange(currentResource.NonComputedProperties); + currentResource.Properties = properties; } break; case ODataReaderState.Primitive: - ODataPrimitiveValue primitive = reader.Item as ODataPrimitiveValue; - propertyValues.Add(primitive.Value); + if (resources.Count == 1) + { + ODataPrimitiveValue primitive = reader.Item as ODataPrimitiveValue; + collectionValues.Add(primitive.Value); + } + break; case ODataReaderState.Stream: - propertyValues.Add(ReadStream(reader)); + string streamValue = ReadStream(reader); + if (resources.Count == 1) + { + collectionValues.Add(streamValue); + } + else if (currentProperty != null) + { + nestedProperties.Peek().Add(new ODataProperty { Name = currentProperty.Name, Value = streamValue }); + } + currentProperty = null; + + break; + + case ODataReaderState.NestedProperty: + currentProperty = reader.Item as ODataPropertyInfo; break; case ODataReaderState.NestedResourceInfoStart: ODataNestedResourceInfo info = reader.Item as ODataNestedResourceInfo; Assert.Equal("untypedCollection", info.Name); - Assert.Null(propertyValues); - propertyValues = new List(); + Assert.Null(collectionValues); + collectionValues = new List(); break; } } - Assert.NotNull(resource); - Assert.Equal(expectedPropertyCount, resource.Properties.Count()); - Assert.NotNull(resource.Properties.FirstOrDefault(p => p.Name == "id")); - Assert.Equal(7, propertyValues.Count); - Assert.Equal(binaryValue, propertyValues[0]); - Assert.Equal(stringValue, propertyValues[1]); - Assert.Equal(stringValue, propertyValues[2]); - Assert.Null(propertyValues[3]); - Assert.Equal(true, propertyValues[4]); - Assert.Equal(false, propertyValues[5]); - Assert.Equal(-10.5m, ((Decimal)propertyValues[6])); + Assert.NotNull(currentResource); + Assert.Equal(expectedPropertyCount, currentResource.Properties.Count()); + Assert.NotNull(currentResource.Properties.FirstOrDefault(p => p.Name == "id")); + Assert.Equal(9, collectionValues.Count); + Assert.Equal(binaryValue, collectionValues[0]); + Assert.Equal(stringValue, collectionValues[1]); + Assert.Equal(stringValue, collectionValues[2]); + ValidateResource(collectionValues[3]); + ValidateResource(collectionValues[4]); + Assert.Equal("test.customer", ((ODataResource)collectionValues[4]).TypeName); + Assert.Null(collectionValues[5]); + Assert.Equal(true, collectionValues[6]); + Assert.Equal(false, collectionValues[7]); + Assert.Equal(-10.5m, (Decimal)collectionValues[8]); } } } + private void ValidateResource(object value) + { + ODataResource resource = value as ODataResource; + Assert.NotNull(resource); + ValidateProperty(resource, "name", "Betty"); + ValidateProperty(resource, "age", 18); + } + + private void ValidateProperty(ODataResource resource, string propertyName, object expectedValue) + { + ODataProperty property = resource.Properties.FirstOrDefault(p => p.Name == propertyName); + if (property != null) + { + Assert.Equal(expectedValue, property.Value); + } + } + [Fact] public void NotStreamingCollectionsWorks() {