Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7caa9d1
Document System.Text.Json updates for .NET 11
eiriktsarpalis Aug 18, 2026
a530ce8
Fix class definitions and update serialization heading
gewarren Sep 23, 2026
957b43d
Merge branch 'main' into eiriktsarpalis-document-stj-dotnet-11
gewarren Sep 23, 2026
8ffd5c9
Update docs/standard/serialization/system-text-json/supported-types.md
gewarren Sep 23, 2026
5e451d9
Preserve Web defaults in JSON metadata example
eiriktsarpalis Sep 24, 2026
a5b4b93
Preserve existing System.Text.Json prose where behavior is unchanged
eiriktsarpalis Sep 24, 2026
1651f81
Lead union docs with JsonSerializer usage and classifier behavior
eiriktsarpalis Sep 24, 2026
9d91804
Describe C# union support as new in .NET 11
eiriktsarpalis Sep 24, 2026
af10466
Avoid redundant reflection and source-generation callouts
eiriktsarpalis Sep 24, 2026
0d5ef64
Let union examples demonstrate JSON serialization
eiriktsarpalis Sep 24, 2026
f2cc852
Document AOT-compatible open generic JSON converters
eiriktsarpalis Sep 24, 2026
548465e
Remove obsolete C# 15 preview requirements from JSON docs
eiriktsarpalis Sep 24, 2026
1de0a19
Introduce union classifier attributes at first use
eiriktsarpalis Sep 24, 2026
e44cddb
Distinguish built-in structural union classifier from custom classifiers
eiriktsarpalis Sep 24, 2026
a610799
Remove routine source-generation setup from union guide
eiriktsarpalis Sep 24, 2026
7ce40cb
Compare union and closed hierarchy JSON contracts
eiriktsarpalis Sep 24, 2026
02c921b
Document union Web defaults and JSON Schema export
eiriktsarpalis Sep 24, 2026
ea934ec
Apply batched suggestions from code review
gewarren Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 113 additions & 56 deletions docs/core/whats-new/dotnet-11/libraries.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/core/whats-new/dotnet-11/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The .NET 11 libraries include new APIs for:
- <xref:System.Diagnostics.Process> expansion with run-and-capture helpers, fire-and-forget launches, <xref:Microsoft.Win32.SafeHandles.SafeProcessHandle> lifecycle methods, tighter handle control, new <xref:System.Diagnostics.ProcessStartInfo.StartSuspended?displayProperty=nameWithType> for suspended starts, <xref:System.Diagnostics.Process.TryGetProcessById(System.Int32,System.Diagnostics.Process@)?displayProperty=nameWithType> for safe process lookup, and <xref:System.Diagnostics.Process.Signal(System.Runtime.InteropServices.PosixSignal)?displayProperty=nameWithType> with <xref:System.Diagnostics.ProcessExitStatus> for signaling processes and inspecting how they exited.
- Compression, including improved Base64 APIs, new methods for ZIP archive entries, Zstandard compression in <xref:System.IO.Compression?displayProperty=fullName>, CRC32 validation when reading ZIP entries, and a `Reset()` method on the streamless Deflate, ZLib, and GZip encoders and decoders.
- New numeric APIs, including IEEE 754 decimal floating-point types (<xref:System.Numerics.Decimal32>, <xref:System.Numerics.Decimal64>, and <xref:System.Numerics.Decimal128>), <xref:System.Numerics.INumberBase`1.TryParsePartial*?displayProperty=nameWithType> for delimiter-aware parsing, and generic <xref:System.Numerics.Complex`1>.
- System.Text.Json improvements, including generic type info retrieval, <xref:System.Text.Json.JsonNamingPolicy.PascalCase?displayProperty=nameWithType>, per-member naming policy overrides, type-level ignore conditions, F# discriminated union support, <xref:System.Text.Json.Utf8JsonWriter.Reset*?displayProperty=nameWithType> with options, `SerializeAsyncEnumerable` overloads for `PipeWriter` targets and top-level values (NDJSON) output, serialization of C# union types with the new `JsonUnionTypeStructuralClassifier`, built-in converters for `BFloat16` and the new decimal floating-point types, and base64 schema metadata from `JsonSchemaExporter`.
- System.Text.Json improvements, including C# and F# union support, JSON Lines (JSONL) output, expanded polymorphism and source generation, new naming and ignore controls, and built-in numeric converters and collection contracts.
- Built-in OpenTelemetry metrics for <xref:Microsoft.Extensions.Caching.Memory.MemoryCache>.
- Discriminated-union scaffolding (`UnionAttribute` and `IUnion`) in <xref:System.Runtime.CompilerServices>.
- Tar archive format selection and GNU sparse format 1.0 support.
Expand Down
41 changes: 34 additions & 7 deletions docs/core/whats-new/dotnet-11/snippets/csharp/Libraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ static void JsonTypeInfoExample()
{
// <JsonTypeInfoGeneric>
JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
options.MakeReadOnly();
options.MakeReadOnly(populateMissingResolver: true);

// Before: manual downcast required
JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));
Expand All @@ -124,17 +124,18 @@ static void JsonNamingIgnoreExample()
{
// <JsonNamingIgnore>
// Type-level JsonIgnore: all members use WhenWritingNull by default
// Type-level JsonNamingPolicy: ReleaseVersion uses snake_case
// Per-member JsonNamingPolicy: EventName uses camelCase even though the
// serializer options use PascalCase
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.PascalCase
};

var data = new EventData { EventName = "Launch", Notes = null };
var data = new EventData { EventName = "Launch", ReleaseVersion = "11", Notes = null };
string json = JsonSerializer.Serialize(data, options);
Console.WriteLine(json);
// {"eventName":"Launch"} -- Notes omitted (null), EventName camel-cased
// {"eventName":"Launch","release_version":"11"} -- Notes omitted (null), EventName camel-cased
// </JsonNamingIgnore>
}

Expand Down Expand Up @@ -309,18 +310,24 @@ static async IAsyncEnumerable<int> GenerateNumbers()
}
}

var pipe = new Pipe();
using var arrayStream = new MemoryStream();
PipeWriter arrayPipe = PipeWriter.Create(arrayStream);

// Write a JSON array: [0,1,2,3,4]
await JsonSerializer.SerializeAsyncEnumerable(
pipe.Writer,
arrayPipe,
GenerateNumbers());
await arrayPipe.CompleteAsync();

// Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
using var jsonlStream = new MemoryStream();
PipeWriter jsonlPipe = PipeWriter.Create(jsonlStream);

// Write JSON Lines (one value per line): 0\n1\n2\n3\n4\n
await JsonSerializer.SerializeAsyncEnumerable(
pipe.Writer,
jsonlPipe,
GenerateNumbers(),
topLevelValues: true);
await jsonlPipe.CompleteAsync();
// </JsonSerializeAsyncEnumerablePipe>
}

Expand All @@ -336,6 +343,17 @@ static void JsonNumericTypesExample()
// </JsonNumericTypes>
}

static void JsonUnionSerializationExample()
{
// <JsonUnionSerialization>
Reading reading = new("hello");
string json = JsonSerializer.Serialize(reading);
Reading copy = JsonSerializer.Deserialize<Reading>(json);
Console.WriteLine(json); // "hello"
Console.WriteLine(copy.Value); // hello
// </JsonUnionSerialization>
}

static void JsonUnionStructuralClassifierExample()
{
// <JsonUnionStructuralClassifier>
Expand Down Expand Up @@ -399,23 +417,32 @@ static void NullableUnderlyingTypeExample()

record MyRecord(string Name, int Value);

[JsonNamingPolicy(JsonKnownNamingPolicy.SnakeCaseLower)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
[JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
public string EventName { get; set; } = "";

public string ReleaseVersion { get; set; } = "";

public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

// <JsonUnionType>
public union Reading(int, string);
// </JsonUnionType>

// <JsonUnionStructuralType>
[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);
// </JsonUnionStructuralType>

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;
2 changes: 2 additions & 0 deletions docs/fundamentals/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@ items:
href: ../standard/serialization/system-text-json/preserve-references.md
- name: Serialize polymorphic types
href: ../standard/serialization/system-text-json/polymorphism.md
- name: Serialize union types
href: ../standard/serialization/system-text-json/union-types.md
- name: Use extension methods on HttpClient
href: ../standard/serialization/system-text-json/httpclient-extensions.md
- name: Read/write JSON without using JsonSerializer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "How to write custom converters for JSON serialization - .NET"
description: "Learn how to create custom converters for the JSON serialization classes that are provided in the System.Text.Json namespace."
ms.date: 03/23/2026
ms.date: 08/18/2026
no-loc: [System.Text.Json, Newtonsoft.Json]
helpviewer_keywords:
- "JSON serialization"
Expand Down Expand Up @@ -89,11 +89,13 @@ The `Enum` type is similar to an open generic type: a converter for `Enum` has t

## Use open generic converters with [JsonConverter]

Starting in .NET 11, <xref:System.Text.Json.Serialization.JsonConverterAttribute> supports open generic converter types on generic types when the total type parameter arity matches. This feature lets you apply a `[JsonConverter]` attribute directly using an open generic converter type (for example, `typeof(OptionConverter<>)`) without implementing a <xref:System.Text.Json.Serialization.JsonConverterFactory>. The serializer automatically constructs the closed generic converter at runtime.
Starting in .NET 11, <xref:System.Text.Json.Serialization.JsonConverterAttribute> supports open generic converter types on generic types when the total type parameter arity matches. This feature lets you apply a `[JsonConverter]` attribute directly using an open generic converter type (for example, `typeof(OptionConverter<>)`) without implementing a <xref:System.Text.Json.Serialization.JsonConverterFactory>. The serializer automatically constructs the closed generic converter.

Unlike the reflection-based factory example above, the source generator resolves the closed converter type at compile time. You can use this pattern with source generation and Native AOT if the converter itself uses AOT-compatible APIs and the generated context provides metadata for the types it handles. The `OptionConverter<T>` example uses `options.GetTypeInfo<T>()` to get metadata for its inner value.

### Define the generic type

Annotate your generic type with `[JsonConverter]`, specifying the open generic converter type. The type parameter count on the converter must match the target type:
Annotate your generic type with `[JsonConverter]`, specifying the open generic converter type. The converter and target type must have matching total generic arity:

:::code language="csharp" source="snippets/converters-how-to/csharp/OpenGenericConverter.cs" id="OptionType":::

Expand Down Expand Up @@ -149,7 +151,7 @@ Continue to use <xref:System.Text.Json.Serialization.JsonConverterFactory> when:
* You register the converter through <xref:System.Text.Json.JsonSerializerOptions.Converters?displayProperty=nameWithType> instead of the `[JsonConverter]` attribute.

> [!NOTE]
> If the type parameter count on the converter doesn't match the target type, an <xref:System.InvalidOperationException> is thrown at runtime.
> At runtime, using an open generic converter on a non-generic type or with mismatched total generic arity throws an <xref:System.InvalidOperationException>. The message identifies the converter and target type.

## The use of `Utf8JsonReader` in the `Read` method

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
title: Custom serialization and deserialization contracts
description: "Learn how to write your own contract resolution logic to customize the JSON contract for a type."
ms.date: 06/15/2023
ms.date: 08/18/2026
ai-usage: ai-assisted
---
# Customize a JSON contract

Expand Down Expand Up @@ -45,15 +46,30 @@ There are two ways to plug into customization. Both involve obtaining a resolver
- If a type isn't handled, <xref:System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo*?displayProperty=nameWithType> should return `null` for that type.
- You can also combine your custom resolver with others, for example, the default resolver. The resolvers will be queried in order until a non-null <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo> value is returned for the type.

## Get strongly typed metadata

Starting in .NET 11, use <xref:System.Text.Json.JsonSerializerOptions.GetTypeInfo``1?displayProperty=nameWithType> and <xref:System.Text.Json.JsonSerializerOptions.TryGetTypeInfo``1(System.Text.Json.Serialization.Metadata.JsonTypeInfo{``0}@)?displayProperty=nameWithType> as strongly typed alternatives to casting the result of <xref:System.Text.Json.JsonSerializerOptions.GetTypeInfo(System.Type)>:

```csharp
JsonTypeInfo<WeatherForecast> typeInfo =
options.GetTypeInfo<WeatherForecast>();

bool found = options.TryGetTypeInfo<WeatherForecast>(
out JsonTypeInfo<WeatherForecast>? optionalTypeInfo);
```

`TryGetTypeInfo<T>` returns `false` when no resolver supplies metadata for `T`.

## Configurable aspects

The <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo.Kind?displayProperty=nameWithType> property indicates how the converter serializes a given type&mdash;for example, as an object or as an array, and whether its properties are serialized. You can query this property to determine which aspects of a type's JSON contract you can configure. There are four different kinds:
The <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo.Kind?displayProperty=nameWithType> property indicates how the converter serializes a given type&mdash;for example, as an object or as an array, and whether its properties are serialized. Query this property to determine which aspects of a type's JSON contract you can configure. The property has five possible values:

| `JsonTypeInfo.Kind` | Description |
|---------------------|-------------|
| <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoKind.Object?displayProperty=nameWithType> | The converter will serialize the type into a JSON object and uses its properties. **This kind is used for most class and struct types and allows for the most flexibility.** |
| <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoKind.Enumerable?displayProperty=nameWithType> | The converter will serialize the type into a JSON array. This kind is used for types like `List<T>` and array. |
| <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoKind.Dictionary?displayProperty=nameWithType> | The converter will serialize the type into a JSON object. This kind is used for types like `Dictionary<K, V>`. |
| <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoKind.Union?displayProperty=nameWithType> | The converter serializes the active case value from a union. Starting in .NET 11, this kind is used for C# union types and exposes case, classifier, constructor, and deconstructor metadata. |
| <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoKind.None?displayProperty=nameWithType> | The converter doesn't specify how it will serialize the type or what `JsonTypeInfo` properties it will use. This kind is used for types like <xref:System.Object?displayProperty=nameWithType>, `int`, and `string`, and for all types that use a custom converter. |

## Modifiers
Expand All @@ -68,6 +84,7 @@ The following table shows the modifications you can make and how to achieve them
| Add or remove properties | `JsonTypeInfoKind.Object` | Add or remove items from the <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo.Properties?displayProperty=nameWithType> list. | [Serialize private fields](#example-serialize-private-fields) |
| Conditionally serialize a property | `JsonTypeInfoKind.Object` | Modify the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.ShouldSerialize?displayProperty=nameWithType> predicate for the property. | [Ignore properties with a specific type](#example-ignore-properties-with-a-specific-type) |
| Customize number handling for a specific type | `JsonTypeInfoKind.None` | Modify the <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo.NumberHandling?displayProperty=nameWithType> value for the type. | [Allow int values to be strings](#example-allow-int-values-to-be-strings) |
| Customize union cases or classification | `JsonTypeInfoKind.Union` | Modify the union cases, classifier, constructor, or deconstructor on <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfo>. | [Serialize union types](union-types.md#customize-a-union-contract) |

## Example: Increment a property's value

Expand Down
Loading
Loading