{"id":5351,"date":"2023-06-19T10:14:32","date_gmt":"2023-06-19T17:14:32","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5351"},"modified":"2023-06-19T10:20:59","modified_gmt":"2023-06-19T17:20:59","slug":"enable-un-typed-within-asp-net-core-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/enable-un-typed-within-asp-net-core-odata\/","title":{"rendered":"Enable Un-typed within ASP.NET Core OData"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>The latest ASP.NET Core OData supports the following two built-in OData abstract types:<\/p>\n<ul>\n<li><strong>Edm.Untyped<\/strong><\/li>\n<li><strong>Collection(Edm.Untyped)<\/strong><\/li>\n<\/ul>\n<p>Developers can use them to advertise a property in OData metadata schema (aka, Edm model) so that such property is declared with a particular name present, but there is no type associated to describe the structure of the property&#8217;s values. Here&#8217;s an example:<\/p>\n<pre class=\"lang:xml decode:true\">\r\n  &lt;Property Name=\"<strong>Data<\/strong>\" Type=\"<strong>Edm.Untyped<\/strong>\"\/&gt;\r\n  &lt;Property Name=\"<strong>Infos<\/strong>\" Type=\"<strong>Collection(Edm.Untyped)<\/strong>\"\/&gt;\r\n<\/pre>\n<p>Where, <strong><em>Data<\/em><\/strong> is called single value untyped property, meanwhile <strong><em>Infos<\/em><\/strong> is called collection value untyped property. Since they are untyped, in other words, there&#8217;s no type limitation for the property value, developers can use <em>any<\/em> kind of values (such as primitive values, structural values, or collection values) to <strong>instantiate<\/strong> such properties. The value assigned to an untyped property is called untyped value. The untyped value can also be used to create dynamic property.<\/p>\n<p>In this post, I&#8217;d like to illustrate the difference between typed and untyped, declared and undeclared properties from the perspective of OData schema using a data model. Meanwhile, I&#8217;d like to share with you the scenarios of untyped value serialization and deserialization to help you understand how to use untyped properties in your own OData service.\nLet\u2019s get started.<\/p>\n<h2>Prerequisites<\/h2>\n<p>I created an ASP.NET Core Web API application named <strong><em>UntypedApp<\/em><\/strong>\u00a0to enable untyped properties in an OData service with <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\" target=\"_blank\" rel=\"noopener\">Microsoft.AspNetCore.OData<\/a>\u00a0(version-8.2.0) installed. In the application, I have the following C# POCO (aka, Plain Old CLR Object) data classes created to build the OData Edm model.<\/p>\n<pre class=\"lang:c# decode:true\">\r\n\/\/ Entity type <strong><em>person<\/em><\/strong>\r\npublic class Person\r\n{\r\n    public int Id { get; set; }\r\n    public string Name { get; set; }\r\n    public Gender Gender { get; set; }\r\n    public object Data { get; set; }\r\n    public IList&lt;object&gt; Infos { get; set; } = new List&lt;object&gt;();\r\n    public IDictionary&lt;string, object&gt; DynamicContainer { get; set; }\r\n}\r\n\r\n\/\/ Complex type <strong><em>Address<\/em><\/strong>\r\npublic class Address\r\n{\r\n    public string City { get; set; }\r\n    public string Street { get; set; }\r\n}\r\n\r\n\/\/ Enum type <strong><em>Gender<\/em><\/strong>\r\npublic enum Gender\r\n{\r\n    Other,\r\n    Male,\r\n    Female\r\n}\r\n<\/pre>\n<p>Meanwhile, I have the following C# POCO data classes which are NOT defined in the OData Edm model.<\/p>\n<pre class=\"lang:c# decode:true\">\r\n\/\/ Complex type <strong><em>Course<\/em><\/strong>\r\npublic class Course\r\n{\r\n    public string Title { get; set; }\r\n    private string AliasName { get =&gt; Title + \"_alias\"; }\r\n    public IList&lt;int&gt; Credits { get; set; }\r\n}\r\n\r\n\/\/ Enum type <strong><em>Color <\/em><\/strong>and<strong><em> UserType<\/em><\/strong>\r\npublic enum Color\r\n{\r\n    Black,\r\n    While,\r\n    Yellow,\r\n    Blue,\r\n    Green\r\n}\r\n\r\npublic enum UserType\r\n{\r\n    Normal,\r\n    Admin\r\n}\r\n<\/pre>\n<h2>Untyped property<\/h2>\n<p>From the perspective of OData spec, a property could be:<\/p>\n<ol>\n<li>A declared property: all properties declared as part of a structured type&#8217;s definition. For example, the property <strong><em>Name<\/em><\/strong> of <strong><em>Person<\/em><\/strong> class.<\/li>\n<li>An un-declared property: all dynamic properties included in the instances of structured types. For example, all items in the <strong><em>DynamicContainer<\/em><\/strong> of <strong><em>Person<\/em><\/strong> class.<\/li>\n<\/ol>\n<p>Basically, a declared property is a named reference to an Edm type, such Edm type should be a <strong>Known<\/strong> type defined in the Edm model. Here, <strong>Known<\/strong> means this type should be resolved from the Edm model using the full-type name. An untyped property is a declared property whose reference type is <em>Edm.Untyped<\/em> or <em>Collection(Edm.Untyped)<\/em>.<\/p>\n<p>In ASP.NET Core OData, it\u2019s easy to build untyped property using the OData model builder following the conventions:<\/p>\n<ul>\n<li>If a property at C# side is defined using <em>System.Object<\/em>, the OData model builder will build it as a single value untyped property, for example <strong><em>Data<\/em><\/strong>.<\/li>\n<li>If a property at C# side is defined using collection of <em>System.Object<\/em>, for example <em>IList&lt;object&gt;<\/em>, the OData model builder will build it as a collection value untyped property, for example <strong><em>Infos<\/em><\/strong>.<\/li>\n<\/ul>\n<p>Based on the above C# data model classes, we can build the Edm model schema containing untyped properties using the model builder as follows:<\/p>\n<pre class=\"lang:c# decode:true\">\r\npublic static IEdmModel GetEdmModel()\r\n{\r\n    var builder = new ODataConventionModelBuilder();\r\n    builder.ComplexType&lt;Address&gt;();\r\n    builder.EntitySet&lt;Person&gt;(\"People\");\r\n    EdmModel model = (EdmModel)builder.GetEdmModel();\r\n\r\n    \/\/ Add a complex type without C# class mapped\r\n    EdmComplexType complexType = new EdmComplexType(\"UntypedApp.Models\", \"Message\");\r\n    complexType.AddStructuralProperty(\"Description\", EdmCoreModel.Instance.GetString(true));\r\n    complexType.AddStructuralProperty(\"Status\", EdmCoreModel.Instance.GetString(false));\r\n    model.AddElement(complexType);\r\n\r\n    return model;\r\n}\r\n<\/pre>\n<p>It builds the Edm model as follows:<\/p>\n<pre class=\"lang:xml decode:true\">\r\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;edmx:Edmx Version=\"4.0\" xmlns:edmx=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edmx\"&gt;\r\n  &lt;edmx:DataServices&gt;\r\n    &lt;Schema Namespace=\"UntypedApp.Models\" xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;ComplexType Name=\"Address\"&gt;\r\n        &lt;Property Name=\"City\" Type=\"Edm.String\" \/&gt;\r\n        &lt;Property Name=\"Street\" Type=\"Edm.String\" \/&gt;\r\n      &lt;\/ComplexType&gt;\r\n      &lt;EntityType Name=\"Person\" OpenType=\"true\"&gt;\r\n        &lt;Key&gt;\r\n          &lt;PropertyRef Name=\"Id\" \/&gt;\r\n        &lt;\/Key&gt;\r\n        &lt;Property Name=\"Id\" Type=\"Edm.Int32\" Nullable=\"false\" \/&gt;\r\n        &lt;Property Name=\"Name\" Type=\"Edm.String\" \/&gt;\r\n        &lt;Property Name=\"Gender\" Type=\"UntypedApp.Models.Gender\" Nullable=\"false\" \/&gt;\r\n        &lt;Property Name=\"Data\" Type=\"Edm.Untyped\" \/&gt;\r\n        &lt;Property Name=\"Infos\" Type=\"Collection(Edm.Untyped)\" \/&gt;\r\n      &lt;\/EntityType&gt;\r\n      &lt;EnumType Name=\"Gender\"&gt;\r\n        &lt;Member Name=\"Other\" Value=\"0\" \/&gt;\r\n        &lt;Member Name=\"Male\" Value=\"1\" \/&gt;\r\n        &lt;Member Name=\"Female\" Value=\"2\" \/&gt;\r\n      &lt;\/EnumType&gt;\r\n      &lt;ComplexType Name=\"Message\"&gt;\r\n        &lt;Property Name=\"Description\" Type=\"Edm.String\" \/&gt;\r\n        &lt;Property Name=\"Status\" Type=\"Edm.String\" Nullable=\"false\" \/&gt;\r\n      &lt;\/ComplexType&gt;\r\n    &lt;\/Schema&gt;\r\n    &lt;Schema Namespace=\"Default\" xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;EntityContainer Name=\"Container\"&gt;\r\n        &lt;EntitySet Name=\"People\" EntityType=\"UntypedApp.Models.Person\" \/&gt;\r\n      &lt;\/EntityContainer&gt;\r\n    &lt;\/Schema&gt;\r\n  &lt;\/edmx:DataServices&gt;\r\n&lt;\/edmx:Edmx&gt;\r\n<\/pre>\n<p>Where,<\/p>\n<ul>\n<li>We have types both defined at C# data model side and at Edm model side as:\n<img decoding=\"async\" class=\"wp-image-5352\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/Untyped_both_types.png\" \/>\n       <\/li>\n<li>We have types only defined at Edm model side, not at C# data side:\n<img decoding=\"async\" class=\"wp-image-5352\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/Untyped_only_edm_types.png\" \/>\n        <\/li>\n<li>We have C# types, but no Edm types defined in the model as:\n<img decoding=\"async\" class=\"wp-image-5352\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/Untyped_only_CLR_types-1.png\" \/>\n        <\/li>\n<\/ul>\n<p>You may notice that we have the following untyped properties:\n<img decoding=\"async\" class=\"wp-image-5352\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/Untyped_propertis.png\" \/><\/p>\n<p>Where,<\/p>\n<ul>\n<li><strong><em>Data<\/em><\/strong> is a <em>System.Object<\/em> typed property at C# side, meanwhile it is an <em>Edm.Untyped<\/em> property at OData Edm schema side. The value of such property MAY be a primitive value, a structural value, or a collection. If a collection, it may contain any combination of primitive values, structural values, and collections.<\/li>\n<li><strong><em>Infos<\/em><\/strong> is a <em>IList&lt;object&gt;<\/em> typed property at C# side, meanwhile it is a <em>Collection(Edm.Untyped)<\/em> property at OData Edm schema side. The value of such property MUST be a collection, and it MAY contain any combination of primitive values, structural values, and collections.<\/li>\n<\/ul>\n<h2>Untyped value<\/h2>\n<p>The value of an untyped property no matter whether it\u2019s an <em>Edm.Untyped<\/em> or <em>Collection(Edm.Untyped)<\/em> property is untyped value. As mentioned above, an untyped value MAY be a primitive value, a structural value, or a collection. If a collection, it may contain any combination of primitive values, structural values, and collections.<\/p>\n<ul>\n<li>Primitive values\n<p>Developers can use all built-in primitive types to create untyped value. Here\u2019s a partial table of mapping between C# primitive types and Edm primitive types.<\/p>\n<table style=\"width:100%\" >\n<thead>\n<tr>\n<th style=\"text-align: center\"><strong>C# Types<\/strong><\/th>\n<th style=\"text-align: center\"><strong>Edm Types<\/strong><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"text-align: center\">byte[]<\/td>\n<td style=\"text-align: center\">Edm.Binary<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">bool<\/td>\n<td style=\"text-align: center\">Edm.Boolean<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">byte<\/td>\n<td style=\"text-align: center\">Edm.Byte<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">string<\/td>\n<td style=\"text-align: center\">Edm.String<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">DateTimeOffset<\/td>\n<td style=\"text-align: center\">Edm.DateTimeOffset<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">decimal<\/td>\n<td style=\"text-align: center\">Edm.Decimal<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">double<\/td>\n<td style=\"text-align: center\">Edm.Double<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">Guid<\/td>\n<td style=\"text-align: center\">Edm.Guid<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">int<\/td>\n<td style=\"text-align: center\">Edm.Int16<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\">\u2026<\/td>\n<td style=\"text-align: center\">\u2026<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In serialization process, developers can use any C# known primitive type. For example,<\/p>\n<pre class=\"lang:C# decode:true\">\r\n    Person aPerson = new Person();\r\n    aPerson.Data = true; \/\/ a System.Boolean\r\n<\/pre>\n<p>In deserialization process, primitive values are read as associated C# primitive instances, for example:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"Data\": 42 \ud83e\udc7a a Edm.Int32 value, read as System.Int32\r\n}\r\n<\/pre>\n<\/li>\n<li>Enum values\n<p>Basically, Enum values are considered as part of primitive value. In serialization process, developers can use any C# Enum type, for example:<\/p>\n<pre class=\"lang:C# decode:true\">\r\n    Person aPerson = new Person();\r\n    aPerson.Data = Gender.Male; \/\/ or\r\n    aPerson.Data = UserType.Admin;\r\n<\/pre>\n<p>In deserialization process, Enum values are read as <em>Edm.String<\/em> value unless they are annotated with the\u00a0<em>@odata.type<\/em>\u00a0control information, in which case they MUST conform to the Enum type described by the control information. If Edm Enum type has C# Enum type associated, we can get an instance of C# Enum type (for example, C# <em>Color<\/em>  Enum type). If Edm Enum type has no C# Enum type associated, we can get an instance of <em>EdmEnumObject<\/em>.<\/p>\n<table style=\"width:100%\" >\n<tbody>\n<tr>\n<td><strong>No <\/strong>@odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data\": \"Red\"\r\n}<\/pre>\n<\/td>\n<td>A string primitive value<\/td>\n<\/tr>\n<tr>\n<td><strong>Has <\/strong>@odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data@odata.type\": \"#Untyped.Models.Color\",\r\n  \"Data\": \"Red\"\r\n}<\/pre>\n<\/td>\n<td>A known Enum value<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li>Structural values\n<p>In serialization process, developers can use any C# class type, for example:<\/p>\n<pre class=\"lang:C# decode:true\">\r\n    Person aPerson = new Person();\r\n    aPerson.Data = new Address { \u2026 }; \/\/ or\r\n    aPerson.Data = new Course { \u2026\u2026 }\r\n<\/pre>\n<p>In deserialization process, structural values are read as an <em>EdmUntypedObject<\/em> instance value unless they are annotated with the\u00a0<em>@odata.type<\/em>\u00a0control information, in which case they MUST conform to the structural type described by the control information. If Edm structural type has C# class type associated, we can get an instance of C# class type (for example, C# <em>Address<\/em> class type). If Edm structural type has no C# class type associated, we can get an instance of <em>EdmComplexObject<\/em> or <em>EdmEntityObject<\/em>.<\/p>\n<table>\n<tbody>\n<tr>\n<td><strong>No <\/strong> @odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data\": {\r\n    \"City\":\"City1\"\r\n  }\r\n}\r\n<\/pre>\n<\/td>\n<td>An untyped structural value<\/td>\n<\/tr>\n<tr>\n<td><strong>Has <\/strong>@odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data\": {\r\n    \"@odata.type\": \"#Untyped.Models.Address\",\r\n    \"City\": \"City1\"\r\n  }\r\n}<\/pre>\n<\/td>\n<td>A known C# class instance<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li>Collection values\n<p>In serialization process, developers can use any C# collection types, for example:<\/p>\n<pre class=\"lang:C# decode:true\">\r\n    Person aPerson = new Person();\r\n    aPerson.Data = new List&lt;Address&gt; { \u2026 }; \/\/ or\r\n    aPerson.Data = new object[] { \u2026\u2026 }\r\n<\/pre>\n<p>In deserialization process, collection values are read as an <em>EdmUntypedCollection<\/em> instance value unless they are annotated with the\u00a0<em>@odata.type<\/em>\u00a0control information on collection property, in which case they MUST conform to the collection type described by the control information. If the collection type has C# class type associated, we can get an instance of C# collection type (for example, C# <em>IList&lt;Address&gt;<\/em> type). If the collection type has no C# class type associated, we can get an instance of <em>EdmComplexCollectionObject<\/em> or <em>EdmEntityCollectionObject<\/em>. If the whole collection is not <em>@odata.type<\/em>\u00a0annotated, we should conform each item based on item\u2019s value and <em>@odata.type<\/em> one by one.<\/p>\n<table>\n<tbody>\n<tr>\n<td><strong>No<\/strong> @odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data\": [\r\n    \u2026\u2026\r\n  ]\r\n}<\/pre>\n<\/td>\n<td>An untyped collection value<\/td>\n<\/tr>\n<tr>\n<td><strong>Has<\/strong> @odata.type<\/td>\n<td>\n<pre class=\"lang:json decode:true\">{\r\n  \"Data@odata.type\": \"#Collection(Edm.Int32)\",\r\n  \"Data\": [\r\n    \u2026\u2026\r\n  ]\r\n}<\/pre>\n<\/td>\n<td>A known C# collection instance<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<\/ul>\n<p>The value of a dynamic property MAY be an untyped value, therefore the above also apply to dynamic properties.<\/p>\n<h2>Untyped value Serialization<\/h2>\n<p>Ok, Let\u2019s look at the detailed untyped value serialization scenarios.<\/p>\n<h2>Primitive as untyped value<\/h2>\n<p>The basic usage is to assign a <strong>known<\/strong> primitive value to an untyped property, such value can also be used for dynamic property. In the application, I have the following <em>person<\/em> data entity:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    new Person\r\n    {\r\n        Id = 1,\r\n        Name = \"Kai\",\r\n        Gender = Gender.Female,\r\n        Data = 42,\r\n        Infos = new object[] { 1, true, 3.1415f, new byte[] { 4, 5 } }\r\n    }\r\n    ......\r\n}\r\n<\/pre>\n<p>Run the application and send a <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/1\">http:\/\/localhost:5299\/odata\/people\/1<\/a><\/p>\n<p>We can get the following OData response:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\": 1,\r\n  \"Name\": \"Kai\",\r\n  \"Gender\": \"Female\",\r\n  \"Data@odata.type\": \"#Int32\",\r\n  \"Data\": 42,\r\n  \"Infos\": [\r\n    1,\r\n    true,\r\n    3.1415,\r\n    \"BAU=\"\r\n  ]\r\n}\r\n<\/pre>\n<p>Be noted:<\/p>\n<ul>\n<li><strong><em>Data<\/em><\/strong> has <em>@odata.type<\/em> control information to specify the real value type as <em>Edm.Int32<\/em>.<\/li>\n<li><strong><em>Infos<\/em><\/strong> doesn\u2019t have <em>@odata.type <\/em>control information because its value is a mix of multiple type values.<\/li>\n<\/ul>\n<h2>Enum as untyped value<\/h2>\n<p>As mentioned, we can use the C# Enum type value as for untyped property value, no matter whether the C# enum type is declared in Edm model or not. In the application, I have the following <em>person<\/em> in the data source:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    ......\r\n    new Person\r\n    {\r\n        Id = 2,\r\n        Name = \"Ezra\",\r\n        Gender = Gender.Male,\r\n        Data = UserType.Admin.ToString(),\r\n        Infos = new object[] { UserType.Admin, 2, Gender.Male },\r\n        DynamicContainer = new Dictionary&lt;string, object&gt;\r\n        {\r\n            { \"D_Data\", UserType.Admin.ToString() }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Be noted, please call <em>ToString()<\/em> if you use un-declared C# Enum type for single untyped value.<\/p>\n<p>Run it and send <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/2\">http:\/\/localhost:5299\/odata\/people\/2<\/a><\/p>\n<p>We can get the following response:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\": 2,\r\n  \"Name\": \"Ezra\",\r\n  \"Gender\": \"Male\",\r\n  \"D_Data\": \"Admin\",\r\n  \"Data@odata.type\": \"#String\",\r\n  \"Data\": \"Admin\",\r\n  \"Infos\": [\r\n    \"Admin\",\r\n    2,\r\n    \"Male\"\r\n  ]\r\n}\r\n<\/pre>\n<p>You may notice that Enum untyped value is serialized as string literal. Specially, <strong><em>Data<\/em><\/strong> has <em>@odata.type<\/em> control information as <em>Edm.String<\/em> since we specify the string value for it. Besides, this entity contains a dyanmic property <strong><em>D_Data<\/em><\/strong> also.<\/p>\n<h2>Resource as untyped value<\/h2>\n<p>We can use any of the following types to serialize a resource for untyped value:<\/p>\n<ul>\n<li>Any C# classes no matter whether it is declared in Edm model<\/li>\n<li><em>IDictionary&lt;string,object&gt;<\/em> or <em>EdmUntypedObject<\/em><\/li>\n<\/ul>\n<h3>C# class<\/h3>\n<p>In the application, I have the following two <em>persons<\/em> in the data source:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    ......\r\n    new Person\r\n    {\r\n        Id = 3,\r\n        Name = \"Chang\",\r\n        Gender = Gender.Male,\r\n        Data = new Course { Title = \"Maths\", Credits = new List&lt;int&gt; { 77, 97 }},\r\n        Infos = new List&lt;object&gt;\r\n        {\r\n            new Address { City = \"Issaquay\", Street = \"Main ST\" }\r\n        }\r\n    },\r\n\r\n    new Person\r\n    {\r\n        Id = 4,\r\n        Name = \"Robert\",\r\n        Gender = Gender.Female,\r\n        Data = new Address { City = \"Redmond\", Street = \"152TH AVE\" },\r\n        Infos = new List&lt;object&gt;\r\n        {\r\n            new Course { Title = \"Geometry\", Credits = new List&lt;int&gt; { 95, 96 }}\r\n        }\r\n    },\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ul>\n<li><strong><em>Data<\/em><\/strong> (3) is an instance of <em>Course<\/em>, meanwhile <strong><em>Data<\/em><\/strong> (4) is an instance of <em>Address<\/em>.<\/li>\n<li><strong><em>Infos<\/em><\/strong> (3) contains instances of <em>Address<\/em> item, meanwhile <strong><em>Infos<\/em><\/strong> (4) contains instance of <em>Course<\/em>.<\/li>\n<li>C# <em>Address<\/em> class is defined as Edm complex type in the model.<\/li>\n<li>C# <em>Course<\/em> class is NOT defined in the model.<\/li>\n<\/ul>\n<p>Run it and send <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/3\">http:\/\/localhost:5299\/odata\/people\/3<\/a><\/p>\n<p>And we can get the following response:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\":\u00a03,\r\n  \"Name\":\u00a0\"Chang\",\r\n  \"Gender\":\u00a0\"Male\",\r\n  \"Data\":\u00a0{\r\n    \"Title\":\u00a0\"Maths\",\r\n    \"Credits@odata.type\":\u00a0\"#Collection(Int32)\",\r\n    \"Credits\":\u00a0[\r\n      77,\r\n      97\r\n    ]\r\n  },\r\n  \"Infos\":\u00a0[\r\n    {\r\n      \"@odata.type\":\u00a0\"#UntypedApp.Models.Address\",\r\n      \"City\":\u00a0\"Issaquay\",\r\n      \"Street\":\u00a0\"Main\u00a0ST\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>You may notice:<\/p>\n<ul>\n<li>If the value type is defined in the model, there\u2019s a <em>@odata.type<\/em> control metadata to indicate the real Edm type, otherwise there\u2019s no such control metadata.<\/li>\n<li>For un-declared C# class (No Edm type defined in the model), only the public properties are retrieved. So, only public properties of <em>Course<\/em> are serialized. Developers can customize this behavior by implementing an untyped value mapper (see below).<\/li>\n<\/ul>\n<p>Try the <a href=\"http:\/\/localhost:5299\/odata\/people\/4\">http:\/\/localhost:5299\/odata\/people\/4<\/a> to see what\u2019s the difference.<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\":\u00a04,\r\n  \"Name\":\u00a0\"Robert\",\r\n  \"Gender\":\u00a0\"Female\",\r\n  \"Data\":\u00a0{\r\n    \"City\":\u00a0\"Redmond\",\r\n    \"Street\":\u00a0\"152TH\u00a0AVE\"\r\n  },\r\n  \"Infos\":\u00a0[\r\n    {\r\n      \"Title\":\u00a0\"Geometry\",\r\n      \"Credits@odata.type\":\u00a0\"#Collection(Int32)\",\r\n      \"Credits\":\u00a0[\r\n        95,\r\n        96\r\n      ]\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>Be noted, <strong><em>Data<\/em><\/strong> should have <em>@odata.type<\/em> associated, but It doesn&#8217;t. It\u2019s an known issue and will be fixed in the next version.<\/p>\n<h3>Dictionary or EdmUntypedObject<\/h3>\n<p>It also supports C# dictionary, which is a key value pair collection. In the application, I have the following <em>person<\/em> entity in the data source:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    ......\r\n    new Person\r\n    {\r\n        Id = 5,\r\n        Name = \"Xu\",\r\n        Gender = Gender.Male,\r\n        Data = new Dictionary&lt;string, object&gt;\r\n        {\r\n            { \"Age\", 8 },\r\n            { \"Email\", \"xu@abc.com\" }\r\n        },\r\n        Infos = new List&lt;object&gt;\r\n        {\r\n            null,\r\n            new Dictionary&lt;string, object&gt;\r\n            {\r\n                { \"Title\", \"Software engineer\" }\r\n            },\r\n            42\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Run it and send <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/People\/5\">http:\/\/localhost:5299\/odata\/People\/5<\/a>, we can get:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\": 5,\r\n  \"Name\": \"Xu\",\r\n  \"Gender\": \"Male\",\r\n  \"Data\": {\r\n    \"Age\": 8,\r\n    \"Email\": \"xu@abc.com\"\r\n  },\r\n  \"Infos\": [\r\n    null,\r\n    {\r\n      \"Title\": \"Software engineer\"\r\n    },\r\n    42\r\n  ]\r\n}\r\n<\/pre>\n<p>Developers can use <em>EdmUntypedObject<\/em> class, which is a derived class from <em>Dictionary&lt;string,object&gt;<\/em>. This class is designed to hold the resource content during deserialization if there\u2019s no <em>@odata.type<\/em> control information associated.<\/p>\n<p>Try the <a href=\"http:\/\/localhost:5299\/odata\/people\/6\">http:\/\/localhost:5299\/odata\/people\/6<\/a> to see the OData payload using <em>EdmUntypedObject<\/em>.<\/p>\n<h2>Collection as untyped value<\/h2>\n<p>The collection value for untyped property can be any combination of primitive values, structural values, and collections. We can use most of generic collection type, such as (<em>IList&lt;T&gt;, Arrray[], ICollection&lt;T&gt;,\u2026<\/em>) or <em>EdmUntypedCollection<\/em> which is derived from <em>List&lt;object&gt;<\/em>.<\/p>\n<p>In the application, I have the following <em>person<\/em> entity in the data source:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    ......\r\n    new Person\r\n    {\r\n        Id = 7,\r\n        Name = \"Wen\",\r\n        Gender = Gender.Male,\r\n        Data = new object[]\r\n        {\r\n            null,\r\n            42,\r\n            new Dictionary<string, object> { { \"City\", \"Shanghai\"} },\r\n            new Course { Title = \"Science\", Credits = new List&lt;int&gt; { 78, 88 }}\r\n        },\r\n        Infos = new EdmUntypedCollection\r\n        {\r\n            new List&lt;object&gt;\r\n            {\r\n                new EdmUntypedCollection\r\n                {\r\n                    null\r\n                }\r\n            },\r\n        },\r\n    },\r\n}\r\n<\/pre>\n<p>Run it and send <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/7\">http:\/\/localhost:5299\/odata\/people\/7<\/a><\/p>\n<p>We can get the OData payload as follows:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\": 7,\r\n  \"Name\": \"Wen\",\r\n  \"Gender\": \"Male\",\r\n  \"Data\": [\r\n    null,\r\n    42,\r\n    {\r\n      \"City\": \"Shanghai\"\r\n    },\r\n    {\r\n      \"Title\": \"Science\",\r\n      \"Credits@odata.type\": \"#Collection(Int32)\",\r\n      \"Credits\": [\r\n        78,\r\n        88\r\n      ]\r\n    }\r\n  ],\r\n  \"Infos\": [\r\n    [\r\n      [\r\n        null\r\n      ]\r\n    ]\r\n  ]\r\n}\r\n<\/pre>\n<p>Be noted, single value untyped property can have single value untyped value and collection untyped value, however, collection value untyped property can only have collection untyped value.<\/p>\n<h2>Customize the Value mapper<\/h2>\n<p>As mentioned, the default serialization process for a C# type instance only retrieves the public properties if such C# type is not defined in the Edm model. Most of the time, it works fine for POCO class. However, the serialization result could be not expected if the resource is not a POCO class. Here\u2019s an example:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nIList&lt;Person&gt; _persons = new List&lt;Person&gt;\r\n{\r\n    ......\r\n    new Person\r\n    {\r\n        Id = 8,\r\n        Name = \"JSON\",\r\n        Gender = Gender.Male,\r\n        Data = JsonDocument.Parse(@\"{\"\"name\"\": \"\"Joe\"\",\"\"age\"\": 22,\"\"canDrive\"\": true}\"),\r\n        Infos = new List&lt;object&gt;(),\r\n    }\r\n}\r\n<\/pre>\n<p>Where, <strong><em>Data<\/em><\/strong> has a <em>System.Text.Json.JsonDocument<\/em> instance.<\/p>\n<p>If we send the <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/8\">http:\/\/localhost:5299\/odata\/people\/8<\/a>, we can get the following result. Obviously it\u2019s unexpected.<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\":\u00a08,\r\n  \"Name\":\u00a0\"JSON\",\r\n  \"Gender\":\u00a0\"Male\",\r\n  \"Data\":\u00a0{\r\n    \"RootElement\":\u00a0{\r\n      \"ValueKind\":\u00a0{}\r\n    }\r\n  },\r\n  \"Infos\":\u00a0[]\r\n}\r\n<\/pre>\n<p>In this case, ASP.NET Core OData enables developers to customize the untyped resource value serialization by implementing <em>IUntypedResourceMapper<\/em> interface. Here\u2019s the interface definition:<\/p>\n<pre class=\"lang:C# decode:true\">\r\npublic interface IUntypedResourceMapper\r\n{\r\n    IDictionary&lt;string, object&gt; Map(object resource, ODataSerializerContext context);\r\n}\r\n<\/pre>\n<p>Developers can start from this interface directly, or start it deriving from the default mapper implementation as:<\/p>\n<pre class=\"lang:C# decode:true\">\r\npublic class MyUntypedResourceMapper : DefaultUntypedResourceMapper\r\n{\r\n    public override IDictionary&lt;string, object&gt; Map(object resource, ODataSerializerContext context)\r\n    {\r\n        \/\/ ...... Omit the codes, please refer to the repos\r\n    }\r\n}\r\n<\/pre>\n<p>Developers should register the implementation as a service into the service container at beginning to make it work:<\/p>\n<pre class=\"lang:C# decode:true\">\r\nbuilder.Services.AddControllers()\r\n    .AddOData(opt =&gt;\r\n        opt.EnableQueryFeatures()\r\n           .AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel(), services =&gt;\r\n             services.AddSingleton&lt;IUntypedResourceMapper, MyUntypedResourceMapper&gt;())\r\n);\r\n<\/pre>\n<p>Ok, run it and resend <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/8\">http:\/\/localhost:5299\/odata\/people\/8<\/a>, we can get the following result:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5299\/odata\/$metadata#People\/$entity\",\r\n  \"Id\":\u00a08,\r\n  \"Name\":\u00a0\"JSON\",\r\n  \"Gender\":\u00a0\"Male\",\r\n  \"Data\":\u00a0{\r\n    \"name\":\u00a0\"Joe\",\r\n    \"age\":\u00a022,\r\n    \"canDrive\":\u00a0<strong>true<\/strong>\r\n  },\r\n \"Infos\":\u00a0[]\r\n}\r\n<\/pre>\n<p>Besides implementing the <em>IUntypedResourceMapper<\/em> interface, developers can also override the <em>CreateUntypedPropertyValue<\/em> virtual method by deriving from <em>ODataResourceSerializer<\/em> to customize the untyped value serialization. Here&#8217;s a sample:<\/p>\n<pre class=\"lang:C# decode:true\">\r\npublic class MyResourceSerializer : ODataResourceSerializer\r\n{\r\n    public MyResourceSerializer(IODataSerializerProvider serializerProvider) : base(serializerProvider)\r\n    { }\r\n\r\n    public override object CreateUntypedPropertyValue(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext, out IEdmTypeReference actualType)\r\n    {\r\n        \/\/ ...... add your logics here to return some values for untyped properties.\r\n        return base.CreateUntypedPropertyValue(structuralProperty, resourceContext, out actualType);\r\n    }\r\n}\r\n<\/pre>\n<p>Be noted:<\/p>\n<ul>\n<li><em>IUntypedResourceMapper<\/em> only handles structural untyped value mapper.<\/li>\n<li><em>CreateUntypedPropertyValue<\/em> can handle more complex scenarios.<\/li>\n<\/ul>\n<h2>Untyped value deserialization<\/h2>\n<p>We can create a person using POST request with request body containing untyped value for untyped properties. It supports primitive values, structural values, or collections. If a collection, it supports any combination of primitive values, structural values, and nested collections.<\/p>\n<h2>Read primitive untyped value<\/h2>\n<p>We can use primitive value for the untyped property as below:<\/p>\n<p>POST <a href=\"http:\/\/localhost:5299\/odata\/people\">http:\/\/localhost:5299\/odata\/people<\/a><\/p>\n<p>Content-Type: <strong>application\/json<\/strong> (omits for others below)<\/p>\n<p>Request Body:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\": \"sam\",\r\n  \"gender\": \"Male\",\r\n  \"data\":42,\r\n  \"infos\": [42, \"str\"]\r\n}\r\n<\/pre>\n<p>At controller action debug view, we can get a <em>person<\/em> instance with the following content:<\/p>\n<p><img decoding=\"async\" width=\"950\" height=\"263\" class=\"wp-image-5352\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-1.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-1.png 950w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-1-300x83.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-1-768x213.png 768w\" sizes=\"(max-width: 950px) 100vw, 950px\" \/><\/p>\n<p>We can use <em>@odata.type<\/em> to annotate the untyped value for the untyped property as below:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\": \"sam\",\r\n  \"gender\": \"Male\",\r\n  \"data@odata.type\":\"Edm.Guid\",\r\n  \"data\": \"2764AFFC-9AC4-42DB-B3D0-68BC1714261F\",\r\n  \"infos@odata.type\":\"Collection(Edm.Guid)\",\r\n  \"infos\": [ \"2764AFFC-9AC4-42DB-B3D0-68BC1714261F\"]\r\n}\r\n<\/pre>\n<p>We can get the following <em>person<\/em> instance in controller action as:\n<img decoding=\"async\" width=\"1093\" height=\"357\" class=\"wp-image-5353\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-2.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-2.png 1093w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-2-300x98.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-2-1024x334.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-2-768x251.png 768w\" sizes=\"(max-width: 1093px) 100vw, 1093px\" \/><\/p>\n<p>The real type of <strong><em>Data<\/em><\/strong> is <em>System.Guid<\/em> (see the immediate window testing). Of course, If we remove <em>@odata.type<\/em> in the request body for <strong><em>Data<\/em><\/strong> property, the real type of <strong><em>Data<\/em><\/strong> property at controller should be <em>System.String.<\/em><\/p>\n<h2>Read Enum untyped value<\/h2>\n<p>We can\u2019t distinguish Enum value from string value, since Enum value is literally a string in the JSON payload. For example, we can send a POST request with the following request body:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\":\u00a0\"sam\",\r\n  \"gender\":\u00a0\"Male\",\r\n  \"data\":\u00a0\"Male\"\r\n}\r\n<\/pre>\n<p>At the controller action, we can get the  property value as <em>System.String<\/em> again.<\/p>\n<p>We can annotate <em>@odata.type<\/em> for enum value if we do want to read a string as an enum member as below:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\":\u00a0\"sam\",\r\n  \"gender\":\u00a0\"Male\",\r\n  \"data@odata.type\":\"#UntypedApp.Models.Gender\",\r\n  \"data\":\u00a0\"Male\"\r\n}\r\n<\/pre>\n<p>We should get a C# Enum instance for <strong><em>Data<\/em><\/strong> property at the controller action. Unfortunately, there\u2019s an issue to support it and OData team will fix it in the next version.<\/p>\n<h2>Read resource untyped value<\/h2>\n<p>We can use resource untyped value for the untyped property as below:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\":\u00a0\"sam\",\r\n  \"gender\":\u00a0\"Male\",\r\n  \"data\":\u00a0{\"City\":\u00a0\"abc\",\u00a0\"Street\":\u00a0\"xzy\"},\r\n  \"infos\":\u00a0[]\r\n}\r\n<\/pre>\n<p>At controller action, we can get a <em>person<\/em> instance with the following content:<\/p>\n<p><img decoding=\"async\" width=\"968\" height=\"209\" class=\"wp-image-5354\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-3.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-3.png 968w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-3-300x65.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-3-768x166.png 768w\" sizes=\"(max-width: 968px) 100vw, 968px\" \/><\/p>\n<p>Where, we get a <em>EdmUntypedObject<\/em> instance for <strong><em>Data<\/em><\/strong>, it\u2019s a dictionary whose key is <em>System.String<\/em>, whose value is <em>System.Object<\/em>.<\/p>\n<p>We can use <em>@odata.type<\/em> to annotate the resource if we do want to read the value as a C# class instance, for example {&#8220;@odata.type&#8221;:&#8221;#UntypedApp.Models.Message&#8221;, &#8230; }. Unfortunately, same as Enum type, there\u2019s an issue to support it and OData team will fix it in the next version.<\/p>\n<h2>Read collection untyped value<\/h2>\n<p>As mentioned, we can use collection value for <em>Edm.Untyped<\/em> property, meanwhile, we can ONLY use collection value for <em>Collection(Edm.Untyped)<\/em> property. If you use non-collection value for collection untyped property, it won\u2019t pass model validation and you will get a reading exception.<\/p>\n<p>Here\u2019s an example to read collection untyped value.<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"name\":\u00a0\"sam\",\r\n  \"gender\":\u00a0\"Male\",\r\n  \"data\":[\r\n     [42],\r\n     {\"k1\":\u00a0\"abc\",\u00a0\"k2\":\u00a042,\u00a0\"k3\":\u00a0{\"a1\":\u00a02,\u00a0\"b2\":\u00a0<strong>null<\/strong>},\u00a0\"k4\":\u00a0[<strong>null<\/strong>,\u00a042]}\r\n  ],\r\n  \"infos\":\u00a0[\r\n   \u00a0<strong>null<\/strong>,\r\n   \u00a042,\r\n   \u00a0[<strong>null<\/strong>,\u00a0<strong>true<\/strong>]\r\n  ]\r\n}\r\n<\/pre>\n<p>Here\u2019s the debug view of <em>person<\/em>:<\/p>\n<p><img decoding=\"async\" width=\"673\" height=\"652\" class=\"wp-image-5355\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-4.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-4.png 673w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-4-300x291.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-4-24x24.png 24w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/06\/word-image-5351-4-48x48.png 48w\" sizes=\"(max-width: 673px) 100vw, 673px\" \/><\/p>\n<p>Where,<\/p>\n<ul>\n<li><strong><em>Data<\/em><\/strong> is a collection containing 2 items, the first one is a primitive value, the second one is a resource value.<\/li>\n<li><strong><em>Infos<\/em><\/strong> is a collection containing 3 items, the first one is null, the second one is a primitive, the third one is a nested collection containing 2 items.<\/li>\n<\/ul>\n<h2>Query untyped property<\/h2>\n<p>We can query the untyped property directly. It\u2019s same as querying other normal properties if we have property query endpoint enabled. In the application, I enabled the <strong><em>Data<\/em><\/strong> and <strong><em>Infos<\/em><\/strong> property querying endpoint. So, we can run it and send <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/5\/data\">http:\/\/localhost:5299\/odata\/people\/5\/data<\/a><\/p>\n<p>We can get:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5299\/odata\/$metadata#People(5)\/Data\/Edm.Untyped\",\r\n  \"Age\":\u00a08,\r\n  \"Email\":\u00a0\"xu@abc.com\"\r\n}\r\n<\/pre>\n<p>Or we can send a <strong>GET<\/strong> request using <a href=\"http:\/\/localhost:5299\/odata\/people\/7\/infos\">http:\/\/localhost:5299\/odata\/people\/7\/infos<\/a><\/p>\n<p>We can get the following:<\/p>\n<pre class=\"lang:json decode:true\">\r\n[\r\n  [\r\n    [\r\n      <strong>null<\/strong>\r\n    ]\r\n  ]\r\n]\r\n<\/pre>\n<p>You may notice this payload is not an OData payload since the JSON object doesn&#8217;t contain <em>@odata.context<\/em>. It\u2019s a known issue OData team will fix in the next version.<\/p>\n<p>That\u2019s it.<\/p>\n<h2>Summary<\/h2>\n<p>This post introduced <em>Edm.Untyped<\/em> and <em>Collection(Edm.Untyped)<\/em> abstract types supported in ASP.NET Core OData 8.x. We also went through the untyped value serialization and deserialization scenarios for untyped properties. By defining an untyped property, it enables OData developers to achieve more usages since there\u2019s no value type kind limitation that we should follow up to read or write the property.<\/p>\n<p>At the end, please do not hesitate to leave your comments below or let me know your thoughts through\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>I uploaded the whole project to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/UntypedApp\" target=\"_blank\" rel=\"noopener\">this<\/a>\u00a0repository. If you find any bug or issue, please file them <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/issues\/new\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction The latest ASP.NET Core OData supports the following two built-in OData abstract types: Edm.Untyped Collection(Edm.Untyped) Developers can use them to advertise a property in OData metadata schema (aka, Edm model) so that such property is declared with a particular name present, but there is no type associated to describe the structure of the property&#8217;s [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":4144,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[1474,48,80],"class_list":["post-5351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","tag-asp-net-core-odata","tag-odata","tag-web-api"],"acf":[],"blog_post_summary":"<p>Introduction The latest ASP.NET Core OData supports the following two built-in OData abstract types: Edm.Untyped Collection(Edm.Untyped) Developers can use them to advertise a property in OData metadata schema (aka, Edm model) so that such property is declared with a particular name present, but there is no type associated to describe the structure of the property&#8217;s [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5351"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5351\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/4144"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=5351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}