-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
.../core/compatibility/serialization/7.0/jsonserializeroptions-copy-constructor.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
--- | ||
title: "Breaking change: JsonSerializerOptions copy constructor includes JsonSerializerContext" | ||
description: Learn about the .NET 7 breaking change in serialization where the JsonSerializerOptions copy constructor now includes JsonSerializerContext. | ||
ms.date: 09/12/2022 | ||
--- | ||
# JsonSerializerOptions copy constructor includes JsonSerializerContext | ||
|
||
With the release of source generation in .NET 6, the <xref:System.Text.Json.JsonSerializerOptions> copy constructor was intentionally made to ignore its <xref:System.Text.Json.Serialization.JsonSerializerContext> state. This made sense at the time since <xref:System.Text.Json.Serialization.JsonSerializerContext> was designed to have a 1:1 relationship with <xref:System.Text.Json.JsonSerializerOptions> instances. In .NET 7, <xref:System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver> replaces <xref:System.Text.Json.Serialization.JsonSerializerContext> to generalize the context, which removes the need for tight coupling between <xref:System.Text.Json.JsonSerializerOptions> and <xref:System.Text.Json.Serialization.JsonSerializerContext>. The copy constructor now includes the <xref:System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver>/<xref:System.Text.Json.Serialization.JsonSerializerContext> information, which could manifest as a breaking change for some scenarios. | ||
|
||
## Previous behavior | ||
|
||
In .NET 6, the following code serializes successfully. The `MyContext` configuration (which doesn't support `Poco2`) is discarded by the copy constructor, and serialization succeeds because the new options instance defaults to using reflection-based serialization. | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions(MyContext.Default.Options); | ||
JsonSerializer.Serialize(new Poco2(), options); | ||
|
||
[JsonSerializable(typeof(Poco1))] | ||
public partial class MyContext : JsonSerializerContext {} | ||
|
||
public class Poco1 {} | ||
public class Poco2 {} | ||
``` | ||
|
||
## New behavior | ||
|
||
Starting in .NET 7, the same code as shown in the [Previous behavior](#previous-behavior) section throws an <xref:System.InvalidOperationException>. That's because the copy constructor now incorporates `MyContext` metadata, which doesn't support `Poco2` contracts. | ||
|
||
## Version introduced | ||
|
||
.NET 7 Preview 7 | ||
|
||
## Type of breaking change | ||
|
||
This change can affect [binary compatibility](../../categories.md#binary-compatibility). | ||
|
||
## Reason for change | ||
|
||
<xref:System.Text.Json.Serialization.JsonSerializerContext> was the only setting ignored by the copy constructor. This behavior was surprising for some users. | ||
|
||
## Recommended action | ||
|
||
If you depend on the .NET 6 behavior, you can manually unset the <xref:System.Text.Json.JsonSerializerOptions.TypeInfoResolver> property to get back reflection-based contract resolution: | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions(MyContext.Default.Options); | ||
options.TypeInfoResolver = null; // Unset `MyContext.Default` as the resolver for the options instance. | ||
``` | ||
|
||
## Affected APIs | ||
|
||
- <xref:System.Text.Json.JsonSerializerOptions.%23ctor(System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> |
74 changes: 74 additions & 0 deletions
74
docs/core/compatibility/serialization/7.0/polymorphic-serialization.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
title: "Breaking change: Polymorphic serialization for object types" | ||
description: Learn about the .NET 7 breaking change in serialization where System.Text.Json no longer hardcodes polymorphism for root-level object types. | ||
ms.date: 09/12/2022 | ||
--- | ||
# Polymorphic serialization for object types | ||
|
||
Using default configuration, <xref:System.Text.Json?displayProperty=fullName> serializes values of type `object` [using polymorphism](../../../../standard/serialization/system-text-json-polymorphism.md). This behavior becomes less consistent if you register a custom converter for `object`. `System.Text.Json` has historically hardcoded polymorphism for root-level object values but not for nested object values. Starting with .NET 7 RC 1, this behavior has changed so that custom converters never use polymorphism. | ||
|
||
## Previous behavior | ||
|
||
Consider the following custom object converter: | ||
|
||
```csharp | ||
public class CustomObjectConverter : JsonConverter<object> | ||
{ | ||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) | ||
=> writer.WriteNumberValue(42); | ||
|
||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
=> throw new NotImplementedException(); | ||
} | ||
``` | ||
|
||
In previous versions, the following code serialized as 0. That's because the serializer used polymorphism and ignored the custom converter. | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions { Converters = { new CustomObjectConverter() } }; | ||
JsonSerializer.Serialize<object>(0, options); | ||
``` | ||
|
||
However, the following code serialized as 42 because the serializer honored the custom converter. | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions { Converters = { new CustomObjectConverter() } }; | ||
JsonSerializer.Serialize<object[]>(new object[] { 0 }, options); | ||
``` | ||
|
||
## New behavior | ||
|
||
Starting in .NET 7, using the custom object converter defined in the [Previous behavior](#previous-behavior) section, the following code serializes as 42. That's because the serializer will always consult the custom converter and not use polymorphism. | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions { Converters = { new CustomObjectConverter() } }; | ||
JsonSerializer.Serialize<object>(0, options); | ||
``` | ||
|
||
## Version introduced | ||
|
||
.NET 7 RC 1 | ||
|
||
## Type of breaking change | ||
|
||
This change can affect [binary compatibility](../../categories.md#binary-compatibility). | ||
|
||
## Reason for change | ||
|
||
This change was made due to inconsistent serialization contracts for a type, depending on whether it was being serialized as a root-level value or a nested value. | ||
|
||
## Recommended action | ||
|
||
If desired, you can get back polymorphism for root-level values by invoking one of the untyped serialization methods: | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions { Converters = { new CustomObjectConverter() } }; | ||
JsonSerializer.Serialize(0, inputType: typeof(int), options); // Serializes as 0. | ||
``` | ||
|
||
## Affected APIs | ||
|
||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(System.IO.Stream,%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(System.Text.Json.Utf8JsonWriter,%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.SerializeAsync%60%601(System.IO.Stream,%60%600,System.Text.Json.JsonSerializerOptions,System.Threading.CancellationToken)?displayProperty=fullName> |
99 changes: 99 additions & 0 deletions
99
docs/core/compatibility/serialization/7.0/reflection-fallback.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
title: "Breaking change: System.Text.Json source generator fallback" | ||
description: Learn about the .NET 7 breaking change in serialization where the System.Text.Json source generator no longer fall backs to reflection-based serialization for unrecognized types. | ||
ms.date: 09/12/2022 | ||
--- | ||
# System.Text.Json source generator fallback | ||
|
||
When using one of the <xref:System.Text.Json.JsonSerializer> methods that accepts <xref:System.Text.Json.JsonSerializerOptions>, the <xref:System.Text.Json?displayProperty=fullName> source generator will no longer implicitly fall back to reflection-based serialization for unrecognized types. | ||
|
||
## Previous behavior | ||
|
||
Consider the following source generation example in .NET 6: | ||
|
||
```csharp | ||
JsonSerializer.Serialize(new Poco2(), typeof(Poco2), MyContext.Default); | ||
|
||
[JsonSerializable(typeof(Poco1))] | ||
public partial class MyContext : JsonSerializerContext {} | ||
|
||
public class Poco1 { } | ||
public class Poco2 { } | ||
``` | ||
|
||
Since `MyContext` does not include `Poco2` in its serializable types, the serialization will correctly fail with the following exception: | ||
|
||
```output | ||
System.InvalidOperationException: | ||
'Metadata for type 'Poco2' was not provided to the serializer. The serializer method used does not | ||
support reflection-based creation of serialization-related type metadata. If using source generation, | ||
ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', | ||
along with any types that might be serialized polymorphically. | ||
``` | ||
|
||
Now consider the following call, which tries to serialize the same type (`MyContext`) using the <xref:System.Text.Json.JsonSerializerOptions> instance constructed by the source generator: | ||
|
||
```csharp | ||
JsonSerializer.Serialize(new Poco2(), MyContext.Default.Options); | ||
``` | ||
|
||
The options instance silently incorporates the default reflection-based contract resolver as a fallback mechanism, and as such, the type serializes successfully—using reflection. | ||
|
||
The same fallback logic applies to <xref:System.Text.Json.JsonSerializerOptions.GetConverter(System.Type)?displayProperty=nameWithType> for options instances that are attached to a <xref:System.Text.Json.Serialization.JsonSerializerContext>. The following statement will return a converter using the built-in reflection converter: | ||
|
||
```csharp | ||
JsonConverter converter = MyContext.Default.Options.GetConverter(typeof(Poco2)); | ||
``` | ||
|
||
## New behavior | ||
|
||
Starting in .NET 7, the following call fails with the same exception (<xref:System.InvalidOperationException>) as when using the <xref:System.Text.Json.Serialization.JsonSerializerContext> overload: | ||
|
||
```csharp | ||
JsonSerializer.Serialize(new Poco2(), MyContext.Default.Options); | ||
``` | ||
|
||
In addition, the following statement will fail with a <xref:System.NotSupportedException>: | ||
|
||
```csharp | ||
JsonConverter converter = MyContext.Default.Options.GetConverter(typeof(Poco2)); | ||
``` | ||
|
||
## Version introduced | ||
|
||
.NET 7 Preview 7 | ||
|
||
## Type of breaking change | ||
|
||
This change can affect [binary compatibility](../../categories.md#binary-compatibility). | ||
|
||
## Reason for change | ||
|
||
The previous behavior violates the principle of least surprise and ultimately defeats the purpose of source generation. With the release of a feature that allows you to [customize the JSON serialization contracts of your types](https://github.com/dotnet/runtime/issues/63686), you have the ability to fine tune the sources of your contract metadata. With this in mind, silently introducing alternative sources becomes even less desirable. | ||
|
||
## Recommended action | ||
|
||
You might depend on the previous behavior, either intentionally or unintentionally. As such, you can use the following workaround to continue to fall back to reflection-based serialization when necessary: | ||
|
||
```csharp | ||
var options = new JsonSerializerOptions | ||
{ | ||
TypeInfoResolver = JsonTypeInfoResolver.Combine(MyContext.Default, new DefaultJsonTypeInfoResolver()); | ||
} | ||
|
||
JsonSerializer.Serialize(new Poco2(), options); // Contract resolution falls back to the default reflection-based resolver. | ||
options.GetConverter(typeof(Poco2)); // Returns the reflection-based converter. | ||
``` | ||
|
||
## Affected APIs | ||
|
||
- <xref:System.Text.Json.JsonSerializerOptions.GetConverter(System.Type)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize(System.IO.Stream,System.Object,System.Type,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize(System.Object,System.Type,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize(System.Text.Json.Utf8JsonWriter,System.Object,System.Type,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(System.IO.Stream,%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.Serialize%60%601(System.Text.Json.Utf8JsonWriter,%60%600,System.Text.Json.JsonSerializerOptions)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.SerializeAsync(System.IO.Stream,System.Object,System.Type,System.Text.Json.JsonSerializerOptions,System.Threading.CancellationToken)?displayProperty=fullName> | ||
- <xref:System.Text.Json.JsonSerializer.SerializeAsync%60%601(System.IO.Stream,%60%600,System.Text.Json.JsonSerializerOptions,System.Threading.CancellationToken)?displayProperty=fullName> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters