{"id":42762,"date":"2022-10-13T08:15:00","date_gmt":"2022-10-13T15:15:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=42762"},"modified":"2022-10-31T10:47:39","modified_gmt":"2022-10-31T17:47:39","slug":"system-text-json-in-dotnet-7","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/system-text-json-in-dotnet-7\/","title":{"rendered":"What&#8217;s new in System.Text.Json in .NET 7"},"content":{"rendered":"<p>In .NET 7, our focus for System.Text.Json has been to substantially improve extensibility of the library, adding new performance-oriented features and addressing high impact reliability and consistency issues. More specifically, .NET 7 sees the release of <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/custom-contracts\">contract customization<\/a>, which gives you more control over how types are serialized or deserialized, polymorphic serialization for user-defined <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/polymorphism#serialize-properties-of-derived-classes\">type hierarchies<\/a>, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/required-properties\">required member support<\/a>, and much more.<\/p>\n<h3>Getting the latest bits<\/h3>\n<p>You can try out the new features by using the latest build of <a href=\"https:\/\/www.nuget.org\/packages\/System.Text.Json\">System.Text.Json NuGet package<\/a> or the <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/7.0\">latest SDK for .NET 7<\/a>, which is currently RC.<\/p>\n<h2><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/63686\">Contract Customization<\/a><\/h2>\n<p>System.Text.Json determines how a given .NET type is meant to be serialized and deserialized by constructing a JSON contract for that type. The contract is derived from the type&#8217;s shape &#8212; such as its available constructors, properties and fields, and whether it implements <code>IEnumerable<\/code> or <code>IDictionary<\/code> &#8212; either at runtime using reflection or at compile time using the source generator. In previous releases, users were able to make limited adjustments to the derived contract using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonattribute\">System.Text.Json attribute annotations<\/a>, assuming they are able to modify the type declaration.<\/p>\n<p>The contract metadata for a given type <code>T<\/code> is represented using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsontypeinfo-1\"><code>JsonTypeInfo&lt;T&gt;<\/code><\/a>, which in previous versions served as an opaque token used exclusively in source generator APIs. Starting in .NET 7, most facets of the <code>JsonTypeInfo<\/code> contract metadata have been exposed and made user-modifiable. Contract customization allows users to write their own JSON contract resolution logic using implementations of the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.ijsontypeinforesolver\"><code>IJsonTypeInfoResolver<\/code><\/a> interface:<\/p>\n<pre><code class=\"language-csharp\">public interface IJsonTypeInfoResolver\r\n{\r\n    JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options);\r\n}<\/code><\/pre>\n<p>A contract resolver returns a configured <code>JsonTypeInfo<\/code> instance for the given <code>Type<\/code> and <code>JsonSerializerOptions<\/code> combination. It can return <code>null<\/code> if the resolver does not support metadata for the specified input type.<\/p>\n<p>Contract resolution performed by the default, reflection-based serializer is now exposed via the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.defaultjsontypeinforesolver\">DefaultJsonTypeInfoResolver<\/a> class, which implements <code>IJsonTypeInfoResolver<\/code>. This class lets users extend the default reflection-based resolution with custom modifications or combine it with other resolvers (such as source-generated resolvers).<\/p>\n<p>Starting from .NET 7 the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonserializercontext\">JsonSerializerContext<\/a> class used in source generation also implements <code>IJsonTypeInfoResolver<\/code>. To learn more about the source generator, see <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/source-generation\">How to use source generation in System.Text.Json<\/a>.<\/p>\n<p>A <code>JsonSerializerOptions<\/code> instance can be configured with a custom resolver using the new <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.jsonserializeroptions.typeinforesolver\"><code>TypeInfoResolver<\/code><\/a> property:<\/p>\n<pre><code class=\"language-C#\">\/\/ configure to use reflection contracts\r\nvar reflectionOptions = new JsonSerializerOptions \r\n{ \r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver()\r\n};\r\n\r\n\/\/ configure to use source generated contracts\r\nvar sourceGenOptions = new JsonSerializerOptions \r\n{ \r\n    TypeInfoResolver = EntryContext.Default \r\n};\r\n\r\n[JsonSerializable(typeof(MyPoco))]\r\npublic partial class EntryContext : JsonSerializerContext { }<\/code><\/pre>\n<h3>Modifying JSON contracts<\/h3>\n<p>The <code>DefaultJsonTypeInfoResolver<\/code> class is designed with contract customization in mind and can be extended in a couple of ways:<\/p>\n<ol>\n<li>Inheriting from the class, overriding the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.defaultjsontypeinforesolver.gettypeinfo\"><code>GetTypeInfo<\/code><\/a> method, or<\/li>\n<li>Using the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.defaultjsontypeinforesolver.modifiers\"><code>Modifiers<\/code><\/a> property to subscribe delegates that modify the default <code>JsonTypeInfo<\/code> results.<\/li>\n<\/ol>\n<p>Here&#8217;s how you could define a custom contract resolver that uses uppercase JSON properties, first using inheritance:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new UpperCasePropertyContractResolver()\r\n};\r\n\r\nJsonSerializer.Serialize(new { value = 42 }, options); \/\/ {\"VALUE\":42}\r\n\r\npublic class UpperCasePropertyContractResolver : DefaultJsonTypeInfoResolver\r\n{\r\n    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)\r\n    {\r\n        JsonTypeInfo typeInfo = base.GetTypeInfo(type, options);\r\n\r\n        if (typeInfo.Kind == JsonTypeInfoKind.Object)\r\n        {\r\n            foreach (JsonPropertyInfo property in typeInfo.Properties)\r\n            {\r\n                property.Name = property.Name.ToUpperInvariant();\r\n            }\r\n        }\r\n\r\n        return typeInfo;\r\n    }\r\n}<\/code><\/pre>\n<p>and the same implementation using the <code>Modifiers<\/code> property:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers = { UseUppercasePropertyNames }\r\n    }\r\n};\r\n\r\nJsonSerializer.Serialize(new { value = 42 }, options); \/\/ {\"VALUE\":42}\r\n\r\nstatic void UseUppercasePropertyNames(JsonTypeInfo jsonTypeInfo)\r\n{\r\n    if (typeInfo.Kind != JsonTypeInfoKind.Object)\r\n        return;\r\n\r\n    foreach (JsonPropertyInfo property in typeInfo.Properties)\r\n    {\r\n        property.Name = property.Name.ToUpperInvariant();\r\n    }\r\n}<\/code><\/pre>\n<p>Each <code>DefaultJsonTypeInfoResolver<\/code> can register multiple modifier delegates, which it runs sequentially on metadata resolved for each type:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers = { Modifier1, Modifier2, Modifier3 }\r\n    }\r\n};\r\n\r\nJsonTypeInfo typeInfo = options.GetTypeInfo(typeof(string)); \/\/ Prints messages in sequence\r\n\r\nstatic void Modifier1(JsonTypeInfo typeInfo) { Console.WriteLine(\"Runs first\"); }\r\nstatic void Modifier2(JsonTypeInfo typeInfo) { Console.WriteLine(\"Runs second\"); }\r\nstatic void Modifier3(JsonTypeInfo typeInfo) { Console.WriteLine(\"Runs third\"); }<\/code><\/pre>\n<p>Modifier syntax affords a more concise and compositional API compared to inheritance. As such, subsequent examples will focus on that approach.<\/p>\n<h4>Notes on authoring contract modifiers<\/h4>\n<p>The <code>DefaultJsonTypeInfoResolver.Modifiers<\/code> property allows developers to specify a series of handlers to update the metadata contract for all types in the serialization type closure. Modifiers are consulted in the order that they were specified in the <code>DefaultJsonTypeInfoResolver.Modifiers<\/code> list. This makes it possible for modifiers to make changes that conflict with each other, possibly resulting in unintended serialization contracts.<\/p>\n<p>For example, consider how the <code>UseUppercasePropertyNames<\/code> modifier above would interact with a modifier that filters out properties that are only used in a hypothetical &#8220;v0&#8221; version of a serialization schema:<\/p>\n<pre><code class=\"language-cs\">JsonSerializerOptions options0 = new()\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    { \r\n        Modifiers = { ExcludeV0Members, UseUppercasePropertyNames } \r\n    }\r\n};\r\n\r\nJsonSerializerOptions options1 = new()\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver \r\n    {\r\n        Modifiers = { UseUppercasePropertyNames, ExcludeV0Members }\r\n    }\r\n};\r\n\r\nEmployee employee = FetchEmployee();\r\nJsonSerializer.Serialize(employee, options0); \/\/ {\"NAME\":\"Jane Doe\",\"ROLE\":\"Contractor\"}\r\nJsonSerializer.Serialize(employee, options1); \/\/ {\"NAME\":\"Jane Doe\",\"ROLE\":\"Contractor\",\"ROLE_V0\":\"Temp\"}\r\n\r\nstatic void ExcludeV0Members(JsonTypeInfo jsonTypeInfo)\r\n{\r\n    if (typeInfo.Kind != JsonTypeInfoKind.Object)\r\n        return;\r\n\r\n    foreach (JsonPropertyInfo property in typeInfo.Properties)\r\n    {\r\n        if (property.Name.EndsWith(\"_v0\"))\r\n        {\r\n            property.ShouldSerialize = false;\r\n        }\r\n    }\r\n}\r\n\r\nclass Employee\r\n{\r\n    public string Name { get; set; }\r\n    public Role Role { get; set; }\r\n    public string Role_v0 =&gt; { get; set; }\r\n}<\/code><\/pre>\n<p>Even though not always possible, it is a good idea to design modifiers with composability in mind. One way to do this is by ensuring that any modifications to the contract are append-only, rather than replacing or removing work done by previous modifiers. Consider the following example, where we define a modifier that appends a property with diagnostic information for every object. Such a modifier might want to confirm that a &#8220;Data&#8221; property does not already exist on a given type:<\/p>\n<pre><code class=\"language-cs\">static void AddDiagnosticDataProperty(JsonTypeInfo typeInfo)\r\n{\r\n    if (typeInfo.Kind == JsonTypeInfoKind.Object &amp;&amp; \r\n        typeInfo.Properties.All(prop =&gt; prop.Name != \"Data\"))\r\n    {\r\n        JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(string, \"Data\");\r\n        propertyInfo.Get = obj =&gt; GetDiagnosticData(obj);\r\n        typeInfo.Properties.Add(propertyInfo);\r\n    }\r\n}<\/code><\/pre>\n<h3>Example: Custom Attribute Support<\/h3>\n<p>Contract customization makes it possible to add support for attributes that are not inbox to <code>System.Text.Json<\/code>. Here is an example implementing support for <code>DataContractSerializer<\/code>&#8216;s <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.runtime.serialization.ignoredatamemberattribute\"><code>IgnoreDataMemberAttribute<\/code><\/a>:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers = { DetectIgnoreDataMemberAttribute }\r\n    }\r\n};\r\n\r\nJsonSerializer.Serialize(new MyPoco(), options); \/\/ {\"Value\":3}\r\n\r\nstatic void DetectIgnoreDataMemberAttribute(JsonTypeInfo typeInfo)\r\n{\r\n    if (typeInfo.Kind != JsonTypeInfoKind.Object)\r\n        return;\r\n\r\n    foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)\r\n    {\r\n        if (propertyInfo.AttributeProvider is ICustomAttributeProvider provider &amp;&amp;\r\n            provider.IsDefined(typeof(IgnoreDataMemberAttribute), inherit: true))\r\n        {\r\n            \/\/ Disable both serialization and deserialization \r\n            \/\/ by unsetting getter and setter delegates\r\n            propertyInfo.Get = null;\r\n            propertyInfo.Set = null;\r\n        }\r\n    }\r\n}\r\n\r\npublic class MyPoco\r\n{\r\n    [JsonIgnore]\r\n    public int JsonIgnoreValue { get; } = 1;\r\n\r\n    [IgnoreDataMember]\r\n    public int IgnoreDataMemberValue { get; } = 2;\r\n\r\n    public int Value { get; } = 3;\r\n}<\/code><\/pre>\n<h3>Example: Conditional Serialization<\/h3>\n<p>You can use the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsonpropertyinfo.shouldserialize\"><code>JsonPropertyInfo.ShouldSerialize<\/code><\/a> delegate to determine dynamically whether a given property value should be serialized:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers = { IgnoreNegativeValues }\r\n    }\r\n};\r\n\r\nJsonSerializer.Serialize(new MyPoco { IgnoreNegativeValues = false, Value = -1 }, options); \/\/ {\"Value\":-1}\r\nJsonSerializer.Serialize(new MyPoco { IgnoreNegativeValues = true, Value = -1 }, options); \/\/ {}\r\n\r\nstatic void IgnoreNegativeValues(JsonTypeInfo typeInfo)\r\n{\r\n    if (typeInfo.Type != typeof(MyPoco))\r\n        return;\r\n\r\n    foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)\r\n    {\r\n        if (propertyInfo.PropertyType == typeof(int))\r\n        {\r\n            propertyInfo.ShouldSerialize = static (obj, value) =&gt; \r\n                (int)value! &gt;= 0 || !((MyPoco)obj).IgnoreNegativeValues;\r\n        }\r\n    }\r\n}\r\n\r\npublic class MyPoco\r\n{\r\n    [JsonIgnore]\r\n    public bool IgnoreNegativeValues { get; set; }\r\n\r\n    public int Value { get; set; }\r\n}<\/code><\/pre>\n<h3>Example: Serializing private fields<\/h3>\n<p>System.Text.Json does not support private field serialization, as doing that is generally not recommended. However, if really necessary it&#8217;s possible to write a contract resolver that only serializes the fields of a given type:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers = { SerializeObjectFields }\r\n    }\r\n};\r\n\r\nJsonSerializer.Serialize(new { value = 42 }, options); \/\/ {\"\\u003Cvalue\\u003Ei__Field\":42}\r\n\r\nstatic void SerializeObjectFields(JsonTypeInfo typeInfo)\r\n{\r\n    if (typeInfo.Kind != JsonTypeInfoKind.Object)\r\n        return;\r\n\r\n    \/\/ Remove any properties included by the default resolver\r\n    typeInfo.Properties.Clear();\r\n\r\n    const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;\r\n    foreach (FieldInfo fieldInfo in typeInfo.Type.GetFields(Flags))\r\n    {\r\n        JsonPropertyInfo propertyInfo = typeInfo.CreateJsonPropertyInfo(fieldInfo.FieldType, fieldInfo.Name);\r\n        propertyInfo.Get = obj =&gt; fieldInfo.GetValue(obj);\r\n        propertyInfo.Set = (obj, value) =&gt; fieldInfo.SetValue(obj, value);\r\n\r\n        typeInfo.Properties.Add(propertyInfo);\r\n    }\r\n}<\/code><\/pre>\n<p>Although as the serialized output would suggest, serializing private fields can be very brittle and error prone. This example has been shared for illustrative purposes only and is not recommended for most real-word applications.<\/p>\n<h3>Combining resolvers<\/h3>\n<p>It is possible to combine contracts from multiple sources using the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsontypeinforesolver.combine\"><code>JsonTypeInfoResolver.Combine<\/code><\/a> method. This is commonly applicable to source generated <code>JsonSerializerContext<\/code> instances that can only generate contracts for a restricted subset of types:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default)\r\n};\r\n\r\n\/\/ Successfully handles serialization for both types\r\nJsonSerializer.Serialize(new PocoA(), options);\r\nJsonSerializer.Serialize(new PocoB(), options);\r\n\r\n\/\/ Types from Library A\r\n[JsonSerializable(typeof(PocoA))]\r\npublic partial class ContextA : JsonSerializerContext { }\r\npublic class PocoA { }\r\n\r\n\/\/ Types from Library B\r\n[JsonSerializable(typeof(PocoB))]\r\npublic partial class ContextB : JsonSerializerContext { }\r\npublic class PocoB { }<\/code><\/pre>\n<p>The <code>Combine<\/code> method produces a contract resolver that sequentially queries each of its constituent resolvers in order of definition, returning the first result that is not <code>null<\/code>. The method supports chaining arbitrarily many resolvers, including <code>DefaultJsonTypeInfoResolver<\/code>:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = JsonTypeInfoResolver.Combine(\r\n        ContextA.Default, \r\n        ContextB.Default, \r\n        new DefaultJsonTypeInfoResolver())\r\n};\r\n\r\n\/\/ Uses reflection for the outer class, source gen for the property types\r\nJsonSerializer.Serialize(new { A = new PocoA(), B = new PocoB() } , options);<\/code><\/pre>\n<h3>Metadata Kinds<\/h3>\n<p><code>JsonTypeInfo<\/code> metadata should be thought of as configuration objects for System.Text.Json&#8217;s built-in converters. As such, what metadata is configurable on each type is largely dependent on the underlying converter being used for the type: types using the built-in converter for objects can configure property metadata, whereas types using custom user-defined converters cannot be configured at all. What can be configured is determined by the value of the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsontypeinfo.kind\"><code>JsonTypeInfo.Kind<\/code><\/a> property. There are currently four kinds of metadata available to the user:<\/p>\n<ul>\n<li><code>Object<\/code> &#8211; type is serialized as a JSON object using <code>JsonPropertyInfo<\/code> metadata; used for most <code>class<\/code> or <code>struct<\/code> types by default.<\/li>\n<li><code>Enumerable<\/code> &#8211; type is serialized as a JSON array; used for most types implementing <code>IEnumerable<\/code>.<\/li>\n<li><code>Dictionary<\/code> &#8211; type is serialized as a JSON object; used for most dictionary types or collections of key\/value pairs.<\/li>\n<li><code>None<\/code> &#8211; type uses a converter not configurable by metadata; typically applies to primitive types, <code>object<\/code>, <code>string<\/code> or types using custom user-defined converters.<\/li>\n<\/ul>\n<p>Currently, the <code>Object<\/code> kind offers the most configurability, and we plan to add more functionality for <code>Enumerable<\/code> and <code>Dictionary<\/code> kinds in future releases.<\/p>\n<h2><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/63747\">Type Hierarchies<\/a><\/h2>\n<p>System.Text.Json now supports polymorphic serialization and deserialization of user-defined type hierarchies. This can be enabled by decorating the base class of a type hierarchy with the new <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonderivedtypeattribute\"><code>JsonDerivedTypeAttribute<\/code><\/a>:<\/p>\n<pre><code class=\"language-csharp\">[JsonDerivedType(typeof(Derived))]\r\npublic class Base\r\n{\r\n    public int X { get; set; }\r\n}\r\n\r\npublic class Derived : Base\r\n{\r\n    public int Y { get; set; }\r\n}<\/code><\/pre>\n<p>This configuration enables polymorphic serialization for <code>Base<\/code>, specifically when the run-time type is <code>Derived<\/code>:<\/p>\n<pre><code class=\"language-csharp\">Base value = new Derived();\r\nJsonSerializer.Serialize&lt;Base&gt;(value); \/\/ { \"X\" : 0, \"Y\" : 0 }<\/code><\/pre>\n<p>Note that this does not enable polymorphic <em>deserialization<\/em> since the payload would be round tripped as <code>Base<\/code>:<\/p>\n<pre><code class=\"language-C#\">Base value = JsonSerializer.Deserialize&lt;Base&gt;(@\"{ \"\"X\"\" : 0, \"\"Y\"\" : 0 }\");\r\nvalue is Derived; \/\/ false<\/code><\/pre>\n<h3>Using Type Discriminators<\/h3>\n<p>To enable polymorphic <em>deserialization<\/em>, you need to specify a <em>type discriminator<\/em> for the derived class:<\/p>\n<pre><code class=\"language-C#\">[JsonDerivedType(typeof(Base), typeDiscriminator: \"base\")]\r\n[JsonDerivedType(typeof(Derived), typeDiscriminator: \"derived\")]\r\npublic class Base\r\n{\r\n    public int X { get; set; }\r\n}\r\n\r\npublic class Derived : Base\r\n{\r\n    public int Y { get; set; }\r\n}<\/code><\/pre>\n<p>Now, type discriminator metadata is emitted in the JSON:<\/p>\n<pre><code class=\"language-C#\">Base value = new Derived();\r\nJsonSerializer.Serialize&lt;Base&gt;(value); \/\/ { \"$type\" : \"derived\", \"X\" : 0, \"Y\" : 0 }<\/code><\/pre>\n<p>The presence of the metadata enables the value to be polymorphically deserialized:<\/p>\n<pre><code class=\"language-C#\">Base value = JsonSerializer.Deserialize&lt;Base&gt;(@\"{ \"\"$type\"\" : \"\"derived\"\", \"\"X\"\" : 0, \"\"Y\"\" : 0 }\");\r\nvalue is Derived; \/\/ true<\/code><\/pre>\n<p>Type discriminator identifiers can also be integers, so the following form is valid:<\/p>\n<pre><code class=\"language-C#\">[JsonDerivedType(typeof(Derived1), 0)]\r\n[JsonDerivedType(typeof(Derived2), 1)]\r\n[JsonDerivedType(typeof(Derived3), 2)]\r\npublic class Base { }\r\n\r\nJsonSerializer.Serialize&lt;Base&gt;(new Derived2()); \/\/ { \"$type\" : 1, ... }<\/code><\/pre>\n<h3>Configuring Polymorphism<\/h3>\n<p>You can tweak aspects of how polymorphic serialization works using the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonpolymorphicattribute\"><code>JsonPolymorphicAttribute<\/code><\/a>. The following example changes the property name of the type discriminator:<\/p>\n<pre><code class=\"language-C#\">[JsonPolymorphic(TypeDiscriminatorPropertyName = \"$case\")]\r\n[JsonDerivedType(typeof(Derived), \"derived\")]\r\npublic class Base { }\r\n\r\nJsonSerializer.Serialize&lt;Base&gt;(new Derived2()); \/\/ { \"$case\" : \"derived\", ... }<\/code><\/pre>\n<p>The <code>JsonPolymorphicAttribute<\/code> exposes a number of configuration parameters, including properties controlling how undeclared runtime types or type discriminators should be handled on serialization and deserialization, respectively.<\/p>\n<h3>Polymorphism using Contract Customization<\/h3>\n<p>The <code>JsonTypeInfo<\/code> contract model exposes the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsontypeinfo.polymorphismoptions\"><code>PolymorphismOptions<\/code><\/a> property that can be used to programmatically control all configuration of a given type hierarchy:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers =\r\n        {\r\n            static typeInfo =&gt;\r\n            {\r\n                if (typeInfo.Type != typeof(Base))\r\n                    return;\r\n\r\n                typeInfo.PolymorphismOptions = new()\r\n                {\r\n                    TypeDiscriminatorPropertyName = \"__type\",\r\n                    DerivedTypes =\r\n                    {\r\n                        new JsonDerivedType(typeof(Derived), \"__derived\")\r\n                    }\r\n                };\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\nJsonSerializer.Serialize&lt;Base&gt;(new Derived(), options); \/\/ {\"__type\":\"__derived\", ... }<\/code><\/pre>\n<h2><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/29861\">Required Members<\/a><\/h2>\n<p>C# 11 adds support for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/whats-new\/csharp-11#required-members\"><code>required<\/code> members<\/a>, a language feature that lets class authors specify properties or fields that <em>must<\/em> be populated on instantiation. Starting in .NET 7, the reflection serializer in System.Text.Json includes support for <code>required<\/code> members: if the member of a deserialized type is marked <code>required<\/code> and cannot bind to any property from the JSON payload, deserialization will fail with an exception:<\/p>\n<pre><code class=\"language-csharp\">using System.Text.Json;\r\n\r\nJsonSerializer.Deserialize&lt;Person&gt;(\"\"\"{\"Age\": 42}\"\"\"); \/\/ throws JsonException\r\n\r\npublic class Person\r\n{\r\n    public required string Name { get; set; }\r\n    public int Age { get; set; }\r\n}<\/code><\/pre>\n<p>It should be noted that <code>required<\/code> properties are currently not supported by the source generator. If you&#8217;re using the source generator, an earlier C# version, a different .NET language like F# or Visual Basic, or simply need to avoid the <code>required<\/code> keyword, you can alternatively use the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonrequiredattribute\"><code>JsonRequiredAttribute<\/code><\/a> to achieve the same effect.<\/p>\n<pre><code class=\"language-csharp\">using System.Text.Json;\r\n\r\nJsonSerializer.Deserialize(\"\"\"{\"Age\": 42}\"\"\", MyContext.Default.Person); \/\/ throws JsonException\r\n\r\n[JsonSerializable(typeof(Person))]\r\npublic partial class MyContext : JsonSerializerContext { }\r\n\r\npublic class Person\r\n{\r\n    [JsonRequired]\r\n    public string Name { get; set; }\r\n    public int Age { get; set; }\r\n}<\/code><\/pre>\n<p>It is also possible to control whether a property is required via the contract model using the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsonpropertyinfo.isrequired\"><code>JsonPropertyInfo.IsRequired<\/code><\/a> property:<\/p>\n<pre><code class=\"language-C#\">var options = new JsonSerializerOptions\r\n{\r\n    TypeInfoResolver = new DefaultJsonTypeInfoResolver\r\n    {\r\n        Modifiers =\r\n        {\r\n            static typeInfo =&gt;\r\n            {\r\n                if (typeInfo.Kind != JsonTypeInfoKind.Object)\r\n                    return;\r\n\r\n                foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)\r\n                {\r\n                    \/\/ strip IsRequired constraint from every property\r\n                    propertyInfo.IsRequired = false;\r\n                }\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\nJsonSerializer.Deserialize&lt;Person&gt;(\"\"\"{\"Age\": 42}\"\"\", options); \/\/ serialization now succeeds<\/code><\/pre>\n<h3><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/61093\">JsonSerializerOptions.Default<\/a><\/h3>\n<p>System.Text.Json maintains a default instance of <code>JsonSerializerOptions<\/code> to be used in cases where no <code>JsonSerializerOptions<\/code> argument has been passed by the user. This (read-only) instance can now be accessed by users via the <code>JsonSerializerOptions.Default<\/code> static property. It can be useful in cases where users need to query the default <code>JsonTypeInfo<\/code> or <code>JsonConverter<\/code> for a given type:<\/p>\n<pre><code class=\"language-C#\">public class MyCustomConverter : JsonConverter&lt;int&gt;\r\n{\r\n    private readonly static JsonConverter&lt;int&gt; s_defaultConverter = \r\n        (JsonConverter&lt;int&gt;)JsonSerializerOptions.Default.GetConverter(typeof(int));\r\n\r\n    \/\/ custom serialization logic\r\n    public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)\r\n    {\r\n        return writer.WriteStringValue(value.ToString());\r\n    }\r\n\r\n    \/\/ fall back to default deserialization logic\r\n    public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\r\n    {\r\n        return s_defaultConverter.Read(ref reader, typeToConvert, options);\r\n    }\r\n}<\/code><\/pre>\n<p>Or in cases where obtaining a <code>JsonSerializerOptions<\/code> is required:<\/p>\n<pre><code class=\"language-C#\">class MyClass\r\n{\r\n    private readonly JsonSerializerOptions _options;\r\n\r\n    public MyClass(JsonSerializerOptions? options = null) =&gt; _options = options ?? JsonSerializerOptions.Default;\r\n}<\/code><\/pre>\n<h2><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/54410\">Utf8JsonReader.CopyString<\/a><\/h2>\n<p>Until today, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.utf8jsonreader.getstring?view=net-6.0\"><code>Utf8JsonReader.GetString()<\/code><\/a> has been the only way users could consume decoded JSON strings. This will always allocate a new string, which might be unsuitable for certain performance-sensitive applications. The newly included <code>CopyString<\/code> methods allow copying the unescaped UTF-8 or UTF-16 strings to a buffer owned by the user:<\/p>\n<pre><code class=\"language-C#\">int valueLength = reader.HasReadOnlySequence \r\n    ? checked((int)ValueSequence.Length) \r\n    : ValueSpan.Length;\r\n\r\nchar[] buffer = ArrayPool&lt;char&gt;.Shared.Rent(valueLength);\r\nint charsRead = reader.CopyString(buffer);\r\nReadOnlySpan&lt;char&gt; source = buffer.Slice(0, charsRead);\r\n\r\nParseUnescapedString(source); \/\/ handle the unescaped JSON string\r\nArrayPool&lt;char&gt;.Shared.Return(buffer);<\/code><\/pre>\n<p>Or if handling UTF-8 is preferable:<\/p>\n<pre><code class=\"language-C#\">ReadOnlySpan&lt;byte&gt; source = stackalloc byte[0];\r\nif (!reader.HasReadOnlySequence &amp;&amp; !reader.ValueIsEscaped)\r\n{\r\n    \/\/ No need to copy to an intermediate buffer if value is span without escape sequences\r\n    source = reader.ValueSpan;\r\n}\r\nelse\r\n{\r\n    int valueLength = reader.HasReadOnlySequence \r\n        ? checked((int)ValueSequence.Length) \r\n        : ValueSpan.Length;\r\n\r\n    Span&lt;byte&gt; buffer = valueLength &lt;= 256 ? stackalloc byte[256] : new byte[valueLength];\r\n    int bytesRead = reader.CopyString(buffer);\r\n    source = buffer.Slice(0, bytesRead);\r\n}\r\n\r\nParseUnescapedBytes(source);<\/code><\/pre>\n<h2>Source generation improvements<\/h2>\n<p>The System.Text.Json source generator now adds built-in support for the following types:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/59268\"><code>IAsyncEnumerable&lt;T&gt;<\/code><\/a>,<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/59954\"><code>JsonDocument<\/code><\/a> and<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/53539\"><code>DateOnly<\/code>\/<code>TimeOnly<\/code><\/a><\/li>\n<\/ul>\n<p>The following example now works as expected:<\/p>\n<pre><code class=\"language-C#\">Stream stdout = Console.OpenStandardOutput();\r\nMyPoco value = new MyPoco();\r\nawait JsonSerializer.SerializeAsync(stdout, value, MyContext.Default.MyPoco);\r\n\r\n[JsonSourceGenerationOptions(WriteIndented = true)]\r\n[JsonSerializable(typeof(MyPoco))]\r\npublic partial class MyContext : JsonSerializerContext\r\n{\r\n}\r\n\r\npublic class MyPoco\r\n{\r\n    public IAsyncEnumerable&lt;DateOnly&gt; Dates =&gt; GetDatesAsync();\r\n\r\n    private async IAsyncEnumerable&lt;DateOnly&gt; GetDatesAsync()\r\n    {\r\n        DateOnly date = DateOnly.Parse(\"2022-09-01\", CultureInfo.InvariantCulture);\r\n        for (int i = 0; i &lt; 10; i++)\r\n        {\r\n            await Task.Delay(1000);\r\n            yield return date.AddDays(i);\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<h2>Performance improvements<\/h2>\n<p>.NET 7 sees the release of the fastest System.Text.Json yet. We have invested in a number of performance-oriented improvements concerning both the internal implementation and user-facing APIs. For a detailed write-up of System.Text.Json performance improvements in .NET 7, please refer to the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance_improvements_in_net_7\/#json\">relevant section<\/a> in Stephen Toub&#8217;s Performance Improvements in .NET 7 article.<\/p>\n<h2>Breaking changes<\/h2>\n<p>As part of our efforts to make System.Text.Json more reliable and consistent, the .NET 7 release includes a number of necessary breaking changes. These typically concern rectifying inconsistencies of components shipped in earlier releases of the library. We have documented each of the breaking changes, including potential workarounds should these impact migration of your apps to .NET 7:<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/compatibility\/serialization\/7.0\/jsonserializeroptions-copy-constructor\">JsonSerializerOptions copy constructor includes JsonSerializerContext<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/compatibility\/serialization\/7.0\/polymorphic-serialization\">Polymorphic serialization for object types<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/compatibility\/serialization\/7.0\/reflection-fallback\">System.Text.Json source generator fallback<\/a><\/li>\n<\/ul>\n<h2>Closing<\/h2>\n<p>Our focus for System.Text.Json this year has been to make the library more extensible and improve its consistency and reliability, while also committing to continually improving performance year-on-year. We&#8217;d like you to try the new features and give us feedback on how it improves your applications, and any usability issues or bugs that you might encounter.<\/p>\n<p>Community contributions are always welcome. If you&#8217;d like to contribute to System.Text.Json, check out our list of <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22\"><code>help wanted<\/code> issues<\/a> on GitHub.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An overview of all .NET 7 features in System.Text.Json<\/p>\n","protected":false},"author":103213,"featured_media":42807,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7590,3009],"tags":[7611,7223],"class_list":["post-42762","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-json","category-performance","tag-dotnet-7","tag-system-text-json"],"acf":[],"blog_post_summary":"<p>An overview of all .NET 7 features in System.Text.Json<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/42762","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/103213"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=42762"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/42762\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/42807"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=42762"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=42762"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=42762"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}