diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index e17ce01c77035..8cd779f5a0218 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -36,6 +36,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs
new file mode 100644
index 0000000000000..9c028f0216517
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json
+{
+ internal static class AppContextSwitchHelper
+ {
+ public static bool IsSourceGenReflectionFallbackEnabled => s_isSourceGenReflectionFallbackEnabled;
+
+ private static readonly bool s_isSourceGenReflectionFallbackEnabled =
+ AppContext.TryGetSwitch(
+ switchName: "System.Text.Json.Serialization.EnableSourceGenReflectionFallback",
+ isEnabled: out bool value)
+ ? value : false;
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
index 596a9c37bae48..fa543671792fb 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
@@ -628,7 +628,20 @@ internal void InitializeForReflectionSerializer()
// Even if a resolver has already been specified, we need to root
// the default resolver to gain access to the default converters.
DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance();
- _typeInfoResolver ??= defaultResolver;
+
+ switch (_typeInfoResolver)
+ {
+ case null:
+ // Use the default reflection-based resolver if no resolver has been specified.
+ _typeInfoResolver = defaultResolver;
+ break;
+
+ case JsonSerializerContext ctx when AppContextSwitchHelper.IsSourceGenReflectionFallbackEnabled:
+ // .NET 6 compatibility mode: enable fallback to reflection metadata for JsonSerializerContext
+ _effectiveJsonTypeInfoResolver = JsonTypeInfoResolver.Combine(ctx, defaultResolver);
+ break;
+ }
+
IsImmutable = true;
_isInitializedForReflectionSerializer = true;
}
@@ -636,6 +649,9 @@ internal void InitializeForReflectionSerializer()
internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer;
private volatile bool _isInitializedForReflectionSerializer;
+ // Only populated in .NET 6 compatibility mode encoding reflection fallback in source gen
+ private IJsonTypeInfoResolver? _effectiveJsonTypeInfoResolver;
+
internal void InitializeForMetadataGeneration()
{
if (_typeInfoResolver is null)
@@ -648,7 +664,7 @@ internal void InitializeForMetadataGeneration()
private JsonTypeInfo? GetTypeInfoNoCaching(Type type)
{
- JsonTypeInfo? info = _typeInfoResolver?.GetTypeInfo(type, this);
+ JsonTypeInfo? info = (_effectiveJsonTypeInfoResolver ?? _typeInfoResolver)?.GetTypeInfo(type, this);
if (info != null)
{
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
index a91467a9943a3..05cd443bccf5a 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
@@ -437,9 +437,18 @@ public static void Options_JsonSerializerContext_DoesNotFallbackToReflection()
}
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
- [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
- public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter()
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(false)]
+ [InlineData(true)]
+ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter(bool isCompatibilitySwitchExplicitlyDisabled)
{
+ var options = new RemoteInvokeOptions();
+
+ if (isCompatibilitySwitchExplicitlyDisabled)
+ {
+ options.RuntimeConfigurationOptions.Add("System.Text.Json.Serialization.EnableSourceGenReflectionFallback", false);
+ }
+
RemoteExecutor.Invoke(static () =>
{
JsonContext context = JsonContext.Default;
@@ -460,7 +469,40 @@ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToR
Assert.Throws(() => context.Options.GetConverter(typeof(MyClass)));
Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, context.Options));
- }).Dispose();
+ }, options).Dispose();
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ public static void Options_JsonSerializerContext_Net6CompatibilitySwitch_FallsBackToReflectionResolver()
+ {
+ var options = new RemoteInvokeOptions
+ {
+ RuntimeConfigurationOptions =
+ {
+ ["System.Text.Json.Serialization.EnableSourceGenReflectionFallback"] = true
+ }
+ };
+
+ RemoteExecutor.Invoke(static () =>
+ {
+ var unsupportedValue = new MyClass { Value = "value" };
+
+ // JsonSerializerContext does not return metadata for the type
+ Assert.Null(JsonContext.Default.GetTypeInfo(typeof(MyClass)));
+
+ // Serialization fails using the JsonSerializerContext overload
+ Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, unsupportedValue.GetType(), JsonContext.Default));
+
+ // Serialization uses reflection fallback using the JsonSerializerOptions overload
+ string json = JsonSerializer.Serialize(unsupportedValue, JsonContext.Default.Options);
+ JsonTestHelper.AssertJsonEqual("""{"Value":"value", "Thing":null}""", json);
+
+ // A converter can be resolved when looking up JsonSerializerOptions
+ JsonConverter converter = JsonContext.Default.Options.GetConverter(typeof(MyClass));
+ Assert.IsAssignableFrom>(converter);
+
+ }, options).Dispose();
}
[Fact]