{"id":47732,"date":"2023-09-19T08:15:00","date_gmt":"2023-09-19T15:15:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=47732"},"modified":"2023-09-26T13:27:07","modified_gmt":"2023-09-26T20:27:07","slug":"system-text-json-in-dotnet-8","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/system-text-json-in-dotnet-8\/","title":{"rendered":"What&#8217;s new in System.Text.Json in .NET 8"},"content":{"rendered":"<p>There are a lot of exciting updates for developers in System.Text.Json in .NET 8. In this release, we have substantially improved the user experience when using the library in Native AOT applications, as well as delivering a number of highly requested features and reliability enhancements. These include support for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/whats-new\/dotnet-8#read-only-properties\">populating read-only members<\/a>, customizable <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/missing-members\">unmapped member handling<\/a>, support for <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/whats-new\/dotnet-8#interface-hierarchies\">interface hierarchies<\/a>, <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/whats-new\/dotnet-8#naming-policies\">snake case and kebab case naming policies<\/a> and much more.<\/p>\n<h3>Getting the latest bits<\/h3>\n<p>You can try out the new features by referencing 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\/8.0\">latest SDK for .NET 8<\/a>, which is currently RC 1.<\/p>\n<h2>Source generator improvements<\/h2>\n<p>This release marks an important milestone in our investment to ensure compatibility with .NET libraries being used in Native AOT applications. System.Text.Json has evolved over the years to use reflection and runtime code generation for serialization and then in .NET 6 the option to use source generators that are compiled into your application. There have always been trade offs between these two options and in this release we have shortened the functional gap between these two options to ensure that ASP.NET Core and other apps have first-class support for source-generated JSON serialization.<\/p>\n<h3><code>required<\/code> and <code>init<\/code> member support<\/h3>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/keywords\/required\">Required members<\/a> and <a href=\"https:\/\/learn.microsoft.com\/dotnet\/csharp\/language-reference\/proposals\/csharp-9.0\/init\">init-only properties<\/a> are relatively recent C# language features that control how fields or properties are meant to be initialized. Even though these have been well-supported by the reflection serializer (the modifiers are compile-time guards that can be bypassed by reflection), this has not been the case with the source generator where referencing <code>required<\/code> or <code>init<\/code> members could result in generated code with compiler errors.<\/p>\n<p>Starting in .NET 8, full support for <code>required<\/code> and <code>init<\/code> members has been added:<\/p>\n<pre><code class=\"language-csharp\">JsonSerializer.Deserialize(\"\"\"{\"Real\" : 0, \"Im\" : 1 }\"\"\", MyContext.Default.Complex);\n\npublic record Complex\n{\n    public required double Real { get; init; }\n    public required double Im { get; init; }\n}\n\n[JsonSerializable(typeof(Complex))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<h3>Combining source generators<\/h3>\n<p>The <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/custom-contracts\">contract customization<\/a> feature introduced in .NET 7 added support for chaining source generators by means of the <code>JsonTypeInfoResolver.Combine<\/code> method. This makes it possible to combine contracts from multiple source generated contexts inside a single <code>JsonSerializerOptions<\/code> instance:<\/p>\n<pre><code class=\"language-csharp\">var options = new JsonSerializerOptions\n{\n    TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default);\n};<\/code><\/pre>\n<p>Based on feedback we\u2019ve received, this approach has a couple of usability issues:<\/p>\n<ol>\n<li>It necessitates specifying all chained components at one call site \u2014 resolvers cannot be prepended or appended to the chain after the fact.<\/li>\n<li>Because the chaining implementation is abstracted behind a <code>IJsonTypeInfoResolver<\/code> implementation, there is no way for users to introspect the chain or remove components from it.<\/li>\n<\/ol>\n<p>The <code>JsonSerializerOptions<\/code> class now includes a <code>TypeInfoResolverChain<\/code> property that is complementary to <code>TypeInfoResolver<\/code>:<\/p>\n<pre><code class=\"language-csharp\">namespace System.Text.Json;\n\npublic partial class JsonSerializerOptions\n{\n    public IJsonTypeInfoResolver? TypeInfoResolver { get; set; }\n    public IList&lt;IJsonTypeInfoResolver&gt; TypeInfoResolverChain { get; }\n}<\/code><\/pre>\n<p>This lets users both introspect and modify the chain of resolvers, as can be seen in the following ASP.NET Core configuration snippet:<\/p>\n<pre><code class=\"language-csharp\">WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args);\n\nbuilder.Services.ConfigureHttpJsonOptions(options =&gt;\n{\n    \/\/ Prepend my source generated context so that it takes precedence over other registered contexts\n    options.SerializerOptions.TypeInfoResolverChain.Insert(0, MyContext.Default);\n});<\/code><\/pre>\n<h3>Unspeakable type support<\/h3>\n<p>Compiler-generated or \u201cunspeakable\u201d types have been challenging to support in weakly typed source gen scenarios. In .NET 7, the following application will fail:<\/p>\n<pre><code class=\"language-csharp\">object value = Test();\nStream stdout = Console.OpenStandardOutput();\nawait JsonSerializer.SerializeAsync(stdout, value, MyContext.Default.Options);\n\nasync IAsyncEnumerable&lt;int&gt; Test()\n{\n    for (int i = 0; i &lt; 5; i++)\n    {\n        await Task.Delay(500);\n        yield return i;\n    }\n}\n\n[JsonSerializable(typeof(IAsyncEnumerable&lt;int&gt;))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<p>producing the error message<\/p>\n<pre><code class=\"language-text\">Metadata for type 'Program+&lt;&lt;&lt;Main&gt;$&gt;g__Test|0_5&gt;d' was not provided by TypeInfoResolver of type 'MyContext'<\/code><\/pre>\n<p>This happens because the compiler-generated type <code>Program+&lt;&lt;&lt;Main&gt;$&gt;g__Test|0_5&gt;d<\/code> cannot be explicitly specified by the source generator.<\/p>\n<p>Starting with .NET 8, System.Text.Json will perform run-time nearest-ancestor resolution to determine the most appropriate supertype with which to serialize the value (in this case, <code>IAsyncEnumerable&lt;int&gt;<\/code>), making the above snippet output a JSON array as expected:<\/p>\n<pre><code class=\"language-text\">[0,1,2,3,4]<\/code><\/pre>\n<h3><code>JsonStringEnumConverter&lt;TEnum&gt;<\/code><\/h3>\n<p>The <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonstringenumconverter\"><code>JsonStringEnumConverter<\/code> class<\/a> is the API of choice for users looking to serialize enum types as string values. The type however requires run-time code generation and as such cannot be used in Native AOT applications.<\/p>\n<p>The new release of System.Text.Json includes the generic <code>JsonStringEnumConverter&lt;TEnum&gt;<\/code> class which does not require run-time code generation. Users wishing to target Native AOT should annotate their enums as follows:<\/p>\n<pre><code class=\"language-csharp\">[JsonConverter(typeof(JsonStringEnumConverter&lt;MyEnum&gt;))]\npublic enum MyEnum { Value1, Value2, Value3 }\n\n[JsonSerializable(typeof(MyEnum))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<p>The source generator will emit diagnostic <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fundamentals\/syslib-diagnostics\/source-generator-overview\">SYSLIB1034<\/a> if it does encounter the unsupported <code>JsonStringEnumConverter<\/code> in the type graph, prompting users to replace it with the new generic type.<\/p>\n<p>String enum conversion can also be applied as a blanket policy in the source generator using the <code>JsonSourceGenerationOptions<\/code> attribute; the following is equivalent to the previous code:<\/p>\n<pre><code class=\"language-csharp\">public enum MyEnum { Value1, Value2, Value3 }\n\n[JsonSourceGenerationOptions(UseStringEnumConverter = true)]\n[JsonSerializable(typeof(MyEnum))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<h3>Extended <code>JsonSourceGenerationOptionsAttribute<\/code> functionality<\/h3>\n<p>The <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.jsonsourcegenerationoptionsattribute\"><code>JsonSourceGenerationOptions<\/code> attribute<\/a> lets users specify compile-time configuration for a small subset of settings available in the <code>JsonSerializerOptions<\/code> class. Users looking to configure the source generator using settings beyond what was available on the attribute needed to manually create a <code>JsonSerializerContext<\/code> instance:<\/p>\n<pre><code class=\"language-csharp\">var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)\n{\n    AllowTrailingCommas = true,\n    DefaultBufferSize = 10\n};\n\nvar context = new MyContext(options);\n\npublic record MyPoco(int Id, string Title);\n\n[JsonSerializable(typeof(MyPoco))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<p>The attribute has now been augmented with most settings available in <code>JsonSerializerOptions<\/code>, so the above can now be rendered as follows:<\/p>\n<pre><code class=\"language-csharp\">MyContext context = MyContext.Default;\n\npublic record MyPoco(int Id, string Title);\n\n[JsonSourceGenerationOptions(\n    JsonSerializerDefaults.Web, \n    AllowTrailingCommas = true, \n    DefaultBufferSize = 10)]\n[JsonSerializable(typeof(MyPoco))]\npublic partial class MyContext : JsonSerializerContext {}<\/code><\/pre>\n<h3>Disabling reflection defaults<\/h3>\n<p>By default, System.Text.Json will use reflection when serializing. Running something as simple as<\/p>\n<pre><code class=\"language-csharp\">JsonSerializer.Serialize(42);<\/code><\/pre>\n<p>either in your code or in a third-party dependency can break your Native AOT application since it requires unsupported reflection under the hood. This can be challenging to diagnose since you&#8217;re most likely debugging your application in CoreCLR where the above works just fine.<\/p>\n<p>It is now possible to disable default reflection in applications using the <code>System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault<\/code> feature switch. This can be turned off using the <code>JsonSerializerIsReflectionEnabledByDefault<\/code> MSBuild property in your project configuration:<\/p>\n<pre><code class=\"language-xml\">&lt;JsonSerializerIsReflectionEnabledByDefault&gt;false&lt;\/JsonSerializerIsReflectionEnabledByDefault&gt;<\/code><\/pre>\n<p>which makes the previous snippet fail with the following error:<\/p>\n<pre><code class=\"language-text\">System.InvalidOperationException: Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.<\/code><\/pre>\n<p>It should be noted that this behavior is consistent regardless of runtime (i.e. both CoreCLR and Native AOT). If left unspecified, the feature switch will be turned off automatically in projects that enable the <code>PublishTrimmed<\/code> property.<\/p>\n<p>The run-time value of the feature switch is reflected by the <code>JsonSerializer.IsReflectionEnabledByDefault<\/code> property. Library authors can consult that value when configuring their serializers:<\/p>\n<pre><code class=\"language-csharp\">static JsonSerializerOptions CreateDefaultOptions()\n{\n    return new()\n    {\n        TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault\n            ? new DefaultJsonTypeInfoResolver()\n            : MyContext.Default\n    };\n}<\/code><\/pre>\n<p>Because the property is treated as a link-time constant, the above method will avoid rooting the reflection-based resolver in applications that do run in Native AOT.<\/p>\n<h3>Size reduction<\/h3>\n<p>We made a number of internal changes that contribute to size reduction of trimmed applications that use System.Text.Json. Consider the following minimal application:<\/p>\n<pre><code class=\"language-csharp\">Todo value = new(1, \"Walk the dog\");\nstring json = JsonSerializer.Serialize(value, MyContext.Default.Todo);\nJsonSerializer.Deserialize(json, MyContext.Default.Todo);\n\npublic record Todo(int Id, string Title);\n\n[JsonSerializable(typeof(Todo))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<p>Publishing as a self-contained Native AOT application in .NET 7 on Windows gives a 3.4 MB binary. On .NET 8 the same application is 2.6 MB, a 23% reduction.<\/p>\n<p>We&#8217;re seeing similar improvements in production apps&mdash;the Microsoft Store team <a href=\"https:\/\/twitter.com\/SergioPedri\/status\/1701633787845546426\">had this to share<\/a>:<\/p>\n<blockquote>\n<p>The Microsoft Store is now happily using System.Text.Json 8.0 RC1 builds, and the new version gave us a whopping 4.2 MB package size improvement compared to 7.0! We&#8217;re also using source generators to make serialization code trim\/AOT-friendly and super efficient \ud83d\ude80<\/p>\n<\/blockquote>\n<h3>Bugfixes<\/h3>\n<p>This release includes a large number of bug fixes, performance improvements, and reliability enhancements to the source generator:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/86121\">dotnet\/runtime#86121<\/a> adds caching support to the incremental generator, improving IDE performance in large projects.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/86526\">dotnet\/runtime#86526<\/a> and <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87557\">dotnet\/runtime#87557<\/a> improve formatting of source generated code, including fixes to a number of indentation issues.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87980\">dotnet\/runtime#87980<\/a> adds a number of new diagnostic warnings.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87136\">dotnet\/runtime#87136<\/a> fixes a number of bugs related to accessibility modifier resolution.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87383\">dotnet\/runtime#87383<\/a> ensures that types of ignored or inaccessible properties are not included by the generator.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87484\">dotnet\/runtime#87484<\/a> fixes issues related to <code>JsonNumberHandling<\/code> support.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87632\">dotnet\/runtime#87632<\/a> fixes support for recursive collection types.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/84208\">dotnet\/runtime#84208<\/a> fixes custom converter support for nullable structs.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87796\">dotnet\/runtime#87796<\/a> fixes a number of bugs in the compile-time attribute parsing implementation.<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/87829\">dotnet\/runtime#87829<\/a> adds support for nesting <code>JsonSerializerContext<\/code> declarations within arbitrary containing types.<\/li>\n<\/ul>\n<h2>Populate read-only members<\/h2>\n<p>Consider the following example:<\/p>\n<pre><code class=\"language-csharp\">MyPoco result = JsonSerializer.Deserialize&lt;MyPoco&gt;(\"\"\"{ \"Values\" : [1,2,3] }\"\"\");\nConsole.WriteLine(result.Values.Count); \/\/ 0\n\npublic class MyPoco\n{\n    public IList&lt;int&gt; Values { get; } = new List&lt;int&gt;();\n}<\/code><\/pre>\n<p>Because the <code>Values<\/code> property doesn&#8217;t include a setter, the serializer will not attempt to bind any data to it, despite the fact that the getter returns a mutable collection that <em>could<\/em> be populated by the serializer. Starting with System.Text.Json v8, this behavior can be configured via the <code>JsonObjectCreationHandling<\/code> attribute:<\/p>\n<pre><code class=\"language-csharp\">MyPoco result = JsonSerializer.Deserialize&lt;MyPoco&gt;(\"\"\"{ \"Values\" : [1,2,3] }\"\"\");\nConsole.WriteLine(result.Values.Count); \/\/ 3\n\npublic class MyPoco\n{\n    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]\n    public IList&lt;int&gt; Values { get; } = new List&lt;int&gt;();\n}<\/code><\/pre>\n<p>Applying the attribute on the type sets the policy on all contained members, where applicable:<\/p>\n<pre><code class=\"language-csharp\">MyPoco result = JsonSerializer.Deserialize&lt;MyPoco&gt;(\"\"\"{ \"Values\" : [1,2,3], \"Person\" : { \"Name\" : \"Brian\" } }\"\"\");\nConsole.WriteLine(result.Values.Count); \/\/ 3\nConsole.WriteLine(result.Person.Name); \/\/ Brian\n\n[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]\npublic class MyPoco\n{\n    public IList&lt;int&gt; Values { get; } = new List&lt;int&gt;();\n\n    public Person Person { get; } = new();\n}\n\npublic class Person\n{\n    public string Name { get; set; }\n}<\/code><\/pre>\n<p>The policy can also be set globally via the <code>PreferredObjectCreationHandling<\/code> property on <code>JsonSerializerOptions<\/code>:<\/p>\n<pre><code class=\"language-csharp\">var options = new JsonSerializerOptions \n{ \n    PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate \n};\n\nMyPoco result = JsonSerializer.Deserialize&lt;MyPoco&gt;(\"\"\"{ \"Values\" : [1,2,3] }\"\"\", options);\nConsole.WriteLine(result.Values.Count); \/\/ 3\n\npublic class MyPoco\n{\n    public IList&lt;int&gt; Values { get; } = new List&lt;int&gt;();\n}<\/code><\/pre>\n<p>or the corresponding property on <code>JsonSourceGenerationOptionsAttribute<\/code> if using the source generator:<\/p>\n<pre><code class=\"language-csharp\">MyPoco result = JsonSerializer.Deserialize(\"\"\"{ \"Values\" : [1,2,3] }\"\"\", MyContext.Default.MyPoco);\nConsole.WriteLine(result.Values.Count); \/\/ 3\n\npublic class MyPoco\n{\n    public IList&lt;int&gt; Values { get; } = new List&lt;int&gt;();\n}\n\n[JsonSourceGenerationOptions(PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate)]\n[JsonSerializable(typeof(MyPoco))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<h3>Final notes<\/h3>\n<ul>\n<li>Struct types can also be populated but this requires that the member is settable.<\/li>\n<li>Population of collections is additive; existing elements in a collection won&#8217;t be cleared and deserialized values will be appended to it.<\/li>\n<li>The population policy can be configured via <a href=\"https:\/\/learn.microsoft.com\/dotnet\/standard\/serialization\/system-text-json\/custom-contracts\">contract customization<\/a> using the <code>JsonPropertyInfo.ObjectCreationHandling<\/code> property.<\/li>\n<\/ul>\n<h2>Missing member handling<\/h2>\n<p>It\u2019s now possible to configure object deserialization behavior, whenever the underlying JSON payload includes properties that cannot be mapped to members of the deserialized POCO type. This can be controlled by applying a <code>JsonUnmappedMemberHandling<\/code> attribute on the POCO type itself, globally using the <code>UnmappedMemberHandling<\/code> property on <code>JsonSerializerOptions<\/code>\/<code>JsonSourceGenerationOptionsAttribute<\/code> or programmatically by customizing the <code>JsonTypeInfo.UnmappedMemberHandling<\/code> property:<\/p>\n<pre><code class=\"language-csharp\">JsonSerializer.Deserialize&lt;MyPoco&gt;(\"\"\"{\"Id\" : 42, \"AnotherId\" : -1 }\"\"\"); \n\/\/ JsonException : The JSON property 'AnotherId' could not be mapped to any .NET member contained in type 'MyPoco'.\n\n[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]\npublic class MyPoco\n{\n   public int Id { get; set; }\n}<\/code><\/pre>\n<h2>Snake case and kebab case naming policies<\/h2>\n<p>The library now ships with naming policies for <code>snake_case<\/code> and <code>kebab-case<\/code> conversions:<\/p>\n<pre><code class=\"language-csharp\">var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };\nJsonSerializer.Serialize(new { PropertyName = \"value\" }, options); \/\/ { \"property_name\" : \"value\" }\n\nvar options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper };\nJsonSerializer.Serialize(new { PropertyName = \"value\" }, options); \/\/ { \"PROPERTY-NAME\" : \"value\" }<\/code><\/pre>\n<p>Here&#8217;s a full list of all available naming policies:<\/p>\n<pre><code class=\"language-csharp\">namespace System.Text.Json;\n\npublic class JsonNamingPolicy\n{\n  public static JsonNamingPolicy CamelCase { get; }\n  public static JsonNamingPolicy KebabCaseLower { get; }\n  public static JsonNamingPolicy KebabCaseUpper { get; }\n  public static JsonNamingPolicy SnakeCaseLower { get; }\n  public static JsonNamingPolicy SnakeCaseUpper { get; }\n}<\/code><\/pre>\n<p>Many thanks to <a href=\"https:\/\/github.com\/YohDeadfall\">@YohDeadfall<\/a> for contributing to the design and implementation of this feature!<\/p>\n<h2>Interface hierarchy support<\/h2>\n<p>The new version fixes support for interface hierarchy serialization. The code:<\/p>\n<pre><code class=\"language-csharp\">IDerived value = new Implementation { Base = 0, Derived = 1 };\nstring json = JsonSerializer.Serialize(value);\nConsole.WriteLine(json);\n\npublic interface IBase\n{\n    public int Base { get; set; }\n}\n\npublic interface IDerived : IBase\n{\n    public int Derived { get; set; }\n}\n\npublic class Implementation : IDerived\n{\n    public int Base { get; set; }\n    public int Derived { get; set; }\n}<\/code><\/pre>\n<p>Will output <code>{\"Derived\":1}<\/code> in .NET 7 and earlier versions. Starting with .NET 8 it will serialize all properties in the type hierarchy:<\/p>\n<pre><code class=\"language-json\">{\"Derived\":1,\"Base\":0}<\/code><\/pre>\n<p>which is similar to how class hierarchies are handled.<\/p>\n<p>It should be noted that interface hierarchies admit multiple inheritance, so in rare cases where there are diamond ambiguities:<\/p>\n<pre><code class=\"language-csharp\">IDiamond value = new Implementation { Value = 0 };\nstring json = JsonSerializer.Serialize(value);\nConsole.WriteLine(json);\n\npublic interface IBase1\n{\n    public int Value { get; set; }\n}\n\npublic interface IBase2\n{\n    public int Value { get; set; }\n}\n\npublic interface IDiamond : IBase1, IBase2 { }\n\npublic class Implementation : IDiamond\n{\n    public int Value { get; set; }\n}<\/code><\/pre>\n<p>the serializer will reject the type altogether:<\/p>\n<pre><code class=\"language-text\">System.InvalidOperationException: The JSON property name for 'IDiamond.Value' collides with another property.<\/code><\/pre>\n<h2>Built-in support for <code>Half<\/code>, <code>Int128<\/code> and <code>UInt128<\/code><\/h2>\n<p>The numeric types <code>System.Half<\/code>, <code>System.Int128<\/code> and <code>System.UInt128<\/code> are now supported out of the box:<\/p>\n<pre><code class=\"language-csharp\">Console.WriteLine(JsonSerializer.Serialize(new object[] { Half.MaxValue, Int128.MaxValue, UInt128.MaxValue }));\n\/\/ [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]<\/code><\/pre>\n<h2>Built-in support for <code>Memory&lt;T&gt;<\/code> and <code>ReadOnlyMemory&lt;T&gt;<\/code><\/h2>\n<p><code>Memory&lt;T&gt;<\/code> and <code>ReadOnlyMemory&lt;T&gt;<\/code> are now supported out of the box, with semantics being equivalent to arrays:<\/p>\n<ul>\n<li>Serializes to Base64 encoded JSON strings for <code>Memory&lt;byte&gt;<\/code> and <code>ReadOnlyMemory&lt;byte&gt;<\/code> values.<\/li>\n<li>Serializes to JSON arrays for all other types.<\/li>\n<\/ul>\n<pre><code class=\"language-csharp\">JsonSerializer.Serialize&lt;ReadOnlyMemory&lt;byte&gt;&gt;(new byte[] { 1, 2, 3 }); \/\/ \"AQID\"\nJsonSerializer.Serialize&lt;ReadOnlyMemory&lt;int&gt;&gt;(new int[] { 1, 2, 3 }); \/\/ [1,2,3]<\/code><\/pre>\n<h2>Single-usage <code>JsonSerializerOptions<\/code> analyzer<\/h2>\n<p>A common source of performance issues in applications using System.Text.Json is single-use <code>JsonSerializerOptions<\/code> instances. Even though the type is nominally just an options type, in actuality it also encapsulates all caches used by the serializer. Writing something as simple as:<\/p>\n<pre><code class=\"language-csharp\">JsonSerializer.Serialize&lt;MyPoco&gt;(value, new JsonSerializerOptions { WriteIndented = true });<\/code><\/pre>\n<p>will result in metadata caches being recomputed <em>on each serialization operation<\/em>. Even though we mitigated some of these performance issues in .NET 7 using a shared cache scheme, it is still the case that caching and reusing <code>JsonSerializerOptions<\/code> singletons in user code is the optimal course of action.<\/p>\n<p>For this purpose, we shipped analyzer <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fundamentals\/code-analysis\/quality-rules\/ca1869\">CA1869<\/a> which will emit a relevant warning whenever it detects single-use options instances.<\/p>\n<h2>Extend <code>JsonIncludeAttribute<\/code> and <code>JsonConstructorAttribute<\/code> support to non-public members<\/h2>\n<p>The <code>JsonIncludeAttribute<\/code> and <code>JsonConstructorAttribute<\/code> are annotations that let users opt specific members into the serialization contract for a given type (properties\/fields and constructors respectively). Until now these were limited to public members, but this has now been relaxed to include non-public members:<\/p>\n<pre><code class=\"language-csharp\">string json = JsonSerializer.Serialize(new MyPoco(42)); \/\/ {\"X\":42}\nJsonSerializer.Deserialize&lt;MyPoco&gt;(json);\n\npublic class MyPoco\n{\n    [JsonConstructor]\n    internal MyPoco(int x) =&gt; X = x;\n\n    [JsonInclude]\n    internal int X { get; }\n}<\/code><\/pre>\n<p>An important caveat to the above is that the source generator is still restricted to members that are visible to generated code, for example the type<\/p>\n<pre><code class=\"language-csharp\">public class MyPoco\n{\n    [JsonInclude]\n    private int value;\n}<\/code><\/pre>\n<p>works as expected in the reflection serializer, but is not supported by the source generator and will result in a <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fundamentals\/syslib-diagnostics\/syslib1038\">SYSLIB1038<\/a> diagnostic warning being issued.<\/p>\n<h2><code>IJsonTypeInfoResolver.WithAddedModifier<\/code><\/h2>\n<p>This new <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.jsontypeinforesolver.withaddedmodifier\">extension method<\/a> enables making modifications to serialization contracts of arbitrary <code>IJsonTypeInfoResolver<\/code> instances, including <code>JsonSerializerContext<\/code>:<\/p>\n<pre><code class=\"language-csharp\">var options = new JsonSerializerOptions\n{\n    TypeInfoResolver = MyContext.Default\n        .WithAddedModifier(static typeInfo =&gt;\n        {\n            foreach (JsonPropertyInfo prop in typeInfo.Properties)\n            {\n                prop.Name = prop.Name.ToUpperInvariant();\n            }\n        })\n};\n\nJsonSerializer.Serialize(new MyPoco(42), options); \/\/ {\"VALUE\":42}\n\npublic record MyPoco(int value);\n\n[JsonSerializable(typeof(MyPoco))]\npublic partial class MyContext : JsonSerializerContext { }<\/code><\/pre>\n<p>In effect, this extends the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json.serialization.metadata.defaultjsontypeinforesolver.modifiers\"><code>DefaultJsonTypeInfoResolver.Modifiers<\/code><\/a> API to any <code>IJsonTypeInfoResolver<\/code> instance.<\/p>\n<h2><code>JsonSerializerOptions.MakeReadOnly()<\/code><\/h2>\n<p>Since released, the <code>JsonSerializerOptions<\/code> type was designed to have freezable semantics. In other words, instances are mutable until the first serialization operation occurs, after which time they can no longer be modified.<\/p>\n<p>The newly added <code>MakeReadOnly<\/code> methods make it possible to explicitly freeze the instance for further modification without requiring a full-blown serialization operation:<\/p>\n<pre><code class=\"language-csharp\">static JsonSerializerOptions CreateDefaultOptions()\n{\n    var options = new JsonSerializerOptions \n    { \n        TypeInfoResolver = new DefaultJsonTypeInfoResolver(),\n        WriteIndented = true\n    };\n\n    options.MakeReadOnly(); \/\/ prevent accidental modification outside the method\n    return options;\n}<\/code><\/pre>\n<p>The type now also comes with an <code>IsReadOnly<\/code> property reflecting its current state:<\/p>\n<pre><code class=\"language-csharp\">JsonSerializerOptions options = new();\nConsole.WriteLine(options.IsReadOnly); \/\/ False\n\nJsonSerializer.Serialize(\"value\", options);\nConsole.WriteLine(options.IsReadOnly); \/\/ True<\/code><\/pre>\n<h2>Additional <code>JsonNode<\/code> functionality<\/h2>\n<p>The <code>JsonNode<\/code> APIs now come with the following new methods:<\/p>\n<pre><code class=\"language-csharp\">namespace System.Text.Json.Nodes;\n\npublic partial class JsonNode\n{\n    \/\/ Creates a deep clone of the current node and all its descendants.\n    public JsonNode DeepClone();\n\n    \/\/ Returns true if the two nodes are equivalent JSON representations.\n    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);\n\n    \/\/ Determines the JsonValueKind of the current node.\n    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);\n\n    \/\/ If node is the value of a property in the parent object, returns its name.\n    public string GetPropertyName();\n\n    \/\/ If node is an element of a parent JsonArray, returns its index.\n    public int GetElementIndex();\n\n    \/\/ Replaces this instance with a new value, updating the parent node accordingly.\n    public void ReplaceWith&lt;T&gt;(T value);\n}\n\npublic partial class JsonArray\n{\n    \/\/ Returns an IEnumerable&lt;T&gt; view of the current array.\n    public IEnumerable&lt;T&gt; GetValues&lt;T&gt;();\n}<\/code><\/pre>\n<p>For example, deep cloning:<\/p>\n<pre><code class=\"language-csharp\">JsonNode node = JsonNode.Parse(\"\"\"{ \"Prop\" : { \"NestedProp\" : 42 }\"\"\");\nJsonNode other = node.DeepClone();\nbool same = JsonNode.DeepEquals(node, other); \/\/ true<\/code><\/pre>\n<p>Support for <code>IEnumerable<\/code>:<\/p>\n<pre><code class=\"language-csharp\">JsonArray jsonArray = new JsonArray(1, 2, 3, 2);\nIEnumerable&lt;int&gt; values = jsonArray.GetValues&lt;int&gt;().Where(i =&gt; i == 2);<\/code><\/pre>\n<h2><code>JsonNode.ParseAsync<\/code> APIs<\/h2>\n<p>Adds support for parsing <code>JsonNode<\/code> instances from streams:<\/p>\n<pre><code class=\"language-csharp\">using var stream = File.OpenRead(\"myFile.json\");\nJsonNode node = await JsonNode.ParseAsync(stream);<\/code><\/pre>\n<h2><code>System.Net.Http.Json<\/code> improvements<\/h2>\n<p>We&#8217;ve shipped a number of new APIs for the separately bundled <code>System.Net.Http.Json<\/code> package.<\/p>\n<h3>IAsyncEnumerable extensions<\/h3>\n<p>.NET 8 sees the inclusion of IAsyncEnumerable streaming deserialization extension methods:<\/p>\n<pre><code class=\"language-csharp\">const string RequestUri = \"https:\/\/api.contoso.com\/books\";\nusing var client = new HttpClient();\nIAsyncEnumerable&lt;Book&gt; books = client.GetFromJsonAsAsyncEnumerable&lt;Book&gt;(RequestUri);\n\nawait foreach (Book book in books)\n{\n    Console.WriteLine($\"Read book '{book.title}'\");\n}\n\npublic record Book(int id, string title, string author, int publishedYear);<\/code><\/pre>\n<h3><code>JsonContent.Create<\/code> overloads accepting <code>JsonTypeInfo<\/code><\/h3>\n<p>It is now possible to create <code>JsonContent<\/code> instances using trim safe\/source generated contracts:<\/p>\n<pre><code class=\"language-csharp\">var book = new Book(id: 42, \"Title\", \"Author\", publishedYear: 2023);\nHttpContent content = JsonContent.Create(book, MyContext.Default.Book);\n\npublic record Book(int id, string title, string author, int publishedYear);\n\n[JsonSerializable(typeof(Book))]\npublic partial class MyContext : JsonSerializerContext \n{ }<\/code><\/pre>\n<h2><code>JsonConverter.Type<\/code> property<\/h2>\n<p>The new property allows users to look up the type of a non-generic <code>JsonConverter<\/code> instance:<\/p>\n<pre><code class=\"language-csharp\">Dictionary&lt;Type, JsonConverter&gt; CreateDictionary(IEnumerable&lt;JsonConverter&gt; converters)\n    =&gt; converters.Where(converter =&gt; converter.Type != null).ToDictionary(converter =&gt; converter.Type!);<\/code><\/pre>\n<p>The property is nullable since it returns <code>null<\/code> for <code>JsonConverterFactory<\/code> instances and <code>typeof(T)<\/code> for <code>JsonConverter&lt;T&gt;<\/code> instances.<\/p>\n<h2>Performance improvements<\/h2>\n<p>For a detailed write-up of System.Text.Json performance improvements in .NET 8, please refer to the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-8\/#json\">relevant section<\/a> in Stephen Toub\u2019s &#8220;Performance Improvements in .NET 8&#8221; article.<\/p>\n<h2>Closing<\/h2>\n<p>.NET 8 sees a large number of new features, reliability enhancements and quality of life improvements with a focus on improved Native AOT support.\nIn total <a href=\"https:\/\/github.com\/dotnet\/runtime\/pulls?q=is%3Apr+is%3Amerged+label%3Aarea-System.Text.Json+milestone%3A8.0.0\">135 pull requests<\/a> were contributed to System.Text.Json during .NET 8 development. 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+label%3Aarea-System.Text.Json\"><code>help wanted<\/code> issues<\/a> on GitHub.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An overview of all new .NET 8 features in System.Text.Json for developers.<\/p>\n","protected":false},"author":103213,"featured_media":47961,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7590],"tags":[7701,7223],"class_list":["post-47732","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-json","tag-dotnet-8","tag-system-text-json"],"acf":[],"blog_post_summary":"<p>An overview of all new .NET 8 features in System.Text.Json for developers.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/47732","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=47732"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/47732\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/47961"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=47732"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=47732"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=47732"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}