From 947e92930e96e6b47494db4d85a8557f6c405651 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 12 Sep 2022 17:30:04 -0700 Subject: [PATCH 1/4] add source gen fallback breaking change --- docs/core/compatibility/7.0.md | 1 + .../serialization/7.0/reflection-fallback.md | 99 +++++++++++++++++++ docs/core/compatibility/toc.yml | 4 + 3 files changed, 104 insertions(+) create mode 100644 docs/core/compatibility/serialization/7.0/reflection-fallback.md diff --git a/docs/core/compatibility/7.0.md b/docs/core/compatibility/7.0.md index 0349500d6ac94..aaf2c3b1eca45 100644 --- a/docs/core/compatibility/7.0.md +++ b/docs/core/compatibility/7.0.md @@ -92,6 +92,7 @@ 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 | +| [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/reflection-fallback.md b/docs/core/compatibility/serialization/7.0/reflection-fallback.md new file mode 100644 index 0000000000000..f680a6bcf685c --- /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 + +Started 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..cf0b448b3da36 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -109,6 +109,8 @@ 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: System.Text.Json source generator fallback + href: serialization/7.0/reflection-fallback.md - name: XML and XSLT items: - name: XmlSecureResolver is obsolete @@ -1133,6 +1135,8 @@ 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: System.Text.Json source generator fallback + href: serialization/7.0/reflection-fallback.md - name: .NET 6 items: - name: Default serialization format for TimeSpan From 02a1e01cf0196a73ee41b6f2b121b9182c9e7c6e Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 12 Sep 2022 18:14:59 -0700 Subject: [PATCH 2/4] copy constructor breaking change --- docs/core/compatibility/7.0.md | 1 + .../jsonserializeroptions-copy-constructor.md | 52 +++++++++++++++++++ .../serialization/7.0/reflection-fallback.md | 2 +- docs/core/compatibility/toc.yml | 4 ++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 docs/core/compatibility/serialization/7.0/jsonserializeroptions-copy-constructor.md diff --git a/docs/core/compatibility/7.0.md b/docs/core/compatibility/7.0.md index aaf2c3b1eca45..6b6e927841a76 100644 --- a/docs/core/compatibility/7.0.md +++ b/docs/core/compatibility/7.0.md @@ -92,6 +92,7 @@ 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 | | [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/reflection-fallback.md b/docs/core/compatibility/serialization/7.0/reflection-fallback.md index f680a6bcf685c..a14e029237aca 100644 --- a/docs/core/compatibility/serialization/7.0/reflection-fallback.md +++ b/docs/core/compatibility/serialization/7.0/reflection-fallback.md @@ -48,7 +48,7 @@ JsonConverter converter = MyContext.Default.Options.GetConverter(typeof(Poco2)); ## New behavior -Started in .NET 7, the following call fails with the same exception () as when using the overload: +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); diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index cf0b448b3da36..323538ebf4730 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -109,6 +109,8 @@ 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: System.Text.Json source generator fallback href: serialization/7.0/reflection-fallback.md - name: XML and XSLT @@ -1135,6 +1137,8 @@ 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: System.Text.Json source generator fallback href: serialization/7.0/reflection-fallback.md - name: .NET 6 From afdfe44dca00ecdc48acf486d0a0bd132ce3aaf1 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Mon, 12 Sep 2022 18:54:44 -0700 Subject: [PATCH 3/4] polymorphic serialization breaking change --- docs/core/compatibility/7.0.md | 1 + .../7.0/polymorphic-serialization.md | 78 +++++++++++++++++++ docs/core/compatibility/toc.yml | 4 + 3 files changed, 83 insertions(+) create mode 100644 docs/core/compatibility/serialization/7.0/polymorphic-serialization.md diff --git a/docs/core/compatibility/7.0.md b/docs/core/compatibility/7.0.md index 6b6e927841a76..106e9da724518 100644 --- a/docs/core/compatibility/7.0.md +++ b/docs/core/compatibility/7.0.md @@ -93,6 +93,7 @@ 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/polymorphic-serialization.md b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md new file mode 100644 index 0000000000000..c88aea9df777b --- /dev/null +++ b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md @@ -0,0 +1,78 @@ +--- +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/toc.yml b/docs/core/compatibility/toc.yml index 323538ebf4730..8bca8e83d0cc4 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -111,6 +111,8 @@ items: 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 @@ -1139,6 +1141,8 @@ items: 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 From 1cbc172678da58dcef9ce3c0555449d0caf7e80f Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 13 Sep 2022 11:17:01 -0700 Subject: [PATCH 4/4] remove untyped apis from affected apis --- .../serialization/7.0/polymorphic-serialization.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md index c88aea9df777b..ed657aa773a90 100644 --- a/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md +++ b/docs/core/compatibility/serialization/7.0/polymorphic-serialization.md @@ -68,11 +68,7 @@ JsonSerializer.Serialize(0, inputType: typeof(int), options); // Serializes as 0 ## Affected APIs -- -- -- - - - -- -