System.Text.Json source generator fallback

When using one of the JsonSerializer methods that accepts JsonSerializerOptions, the System.Text.Json 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:

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:

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 JsonSerializerOptions instance constructed by the source generator:

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 JsonSerializerOptions.GetConverter(Type) for options instances that are attached to a JsonSerializerContext. The following statement will return a converter using the built-in reflection converter:

JsonConverter converter = MyContext.Default.Options.GetConverter(typeof(Poco2));

New behavior

Starting in .NET 7, the following call fails with the same exception (InvalidOperationException) as when using the JsonSerializerContext overload:

JsonSerializer.Serialize(new Poco2(), MyContext.Default.Options);

In addition, the following statement will fail with a NotSupportedException:

JsonConverter converter = MyContext.Default.Options.GetConverter(typeof(Poco2));

Version introduced

.NET 7

Type of breaking change

This change can affect 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, 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.

You might depend on the previous behavior, either intentionally or unintentionally. The recommended course of action is to update your JsonSerializerContext definition so that it includes all type dependencies:

[JsonSerializable(typeof(Poco1))]
[JsonSerializable(typeof(Poco2))]
public partial class MyContext : JsonSerializerContext {}

This will let your application take full advantage of the benefits of source generation, including trim safety.

In certain cases, however, making such a change might not be practical or possible. Even though it's not recommended, there are a couple of ways you can re-enable reflection fallback in your source-generated serializer.

Use a custom contract resolver

You can use the new contract customization feature to build a custom contract resolver that falls back to reflection-based resolution where required:

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.

Use an AppContext switch

Starting in .NET 7, you can re-enable reflection fallback globally using the provided AppContext compatibility switch. Add the following entry to your application's project file to re-enable reflection fallback for all source-generated contexts in your app. For more information on using AppContext switches, see the article on .NET runtime configuration settings.

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.EnableSourceGenReflectionFallback" Value="true" />
</ItemGroup>

Affected APIs