diff --git a/docs/core/compatibility/7.0.md b/docs/core/compatibility/7.0.md index 0349500d6ac94..106e9da724518 100644 --- a/docs/core/compatibility/7.0.md +++ b/docs/core/compatibility/7.0.md @@ -92,6 +92,9 @@ If you're migrating an app to .NET 7, the breaking changes listed here might aff | - | :-: | :-: | - | | [DataContractSerializer retains sign when deserializing -0](serialization/7.0/datacontractserializer-negative-sign.md) | ❌ | ✔️ | RC 1 | | [Deserialize Version type with leading or trailing whitespace](serialization/7.0/deserialize-version-with-whitespace.md) | ❌ | ✔️ | Preview 1 | +| [JsonSerializerOptions copy constructor includes JsonSerializerContext](serialization/7.0/jsonserializeroptions-copy-constructor.md) | ❌ | ✔️ | Preview 7 | +| [Polymorphic serialization for object types](serialization/7.0/polymorphic-serialization.md) | ❌ | ✔️ | RC 1 | +| [System.Text.Json source generator fallback](serialization/7.0/reflection-fallback.md) | ❌ | ✔️ | Preview 7 | ## Windows Forms diff --git a/docs/core/compatibility/serialization/7.0/jsonserializeroptions-copy-constructor.md b/docs/core/compatibility/serialization/7.0/jsonserializeroptions-copy-constructor.md new file mode 100644 index 0000000000000..1abcaf6b98848 --- /dev/null +++ b/docs/core/compatibility/serialization/7.0/jsonserializeroptions-copy-constructor.md @@ -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 copy constructor was intentionally made to ignore its state. This made sense at the time since was designed to have a 1:1 relationship with instances. In .NET 7, replaces to generalize the context, which removes the need for tight coupling between and . The copy constructor now includes the / 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 . 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 + + 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 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 + +- diff --git a/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md new file mode 100644 index 0000000000000..ed657aa773a90 --- /dev/null +++ b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md @@ -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, 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 +{ + 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(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(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(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 + +- +- +- +- diff --git a/docs/core/compatibility/serialization/7.0/reflection-fallback.md b/docs/core/compatibility/serialization/7.0/reflection-fallback.md new file mode 100644 index 0000000000000..a14e029237aca --- /dev/null +++ b/docs/core/compatibility/serialization/7.0/reflection-fallback.md @@ -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 methods that accepts , the 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 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 for options instances that are attached to a . 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 () as when using the overload: + +```csharp +JsonSerializer.Serialize(new Poco2(), MyContext.Default.Options); +``` + +In addition, the following statement will fail with a : + +```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 + +- +- +- +- +- +- +- +- +- diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index d81fe01f7e39a..8bca8e83d0cc4 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -109,6 +109,12 @@ items: href: serialization/7.0/datacontractserializer-negative-sign.md - name: Deserialize Version type with leading or trailing whitespace href: serialization/7.0/deserialize-version-with-whitespace.md + - name: JsonSerializerOptions copy constructor includes JsonSerializerContext + href: serialization/7.0/jsonserializeroptions-copy-constructor.md + - name: Polymorphic serialization for object types + href: serialization/7.0/polymorphic-serialization.md + - name: System.Text.Json source generator fallback + href: serialization/7.0/reflection-fallback.md - name: XML and XSLT items: - name: XmlSecureResolver is obsolete @@ -1133,6 +1139,12 @@ items: href: serialization/7.0/datacontractserializer-negative-sign.md - name: Deserialize Version type with leading or trailing whitespace href: serialization/7.0/deserialize-version-with-whitespace.md + - name: JsonSerializerOptions copy constructor includes JsonSerializerContext + href: serialization/7.0/jsonserializeroptions-copy-constructor.md + - name: Polymorphic serialization for object types + href: serialization/7.0/polymorphic-serialization.md + - name: System.Text.Json source generator fallback + href: serialization/7.0/reflection-fallback.md - name: .NET 6 items: - name: Default serialization format for TimeSpan