<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
+ <Compile Include="System\Text\Json\AppContextSwitchHelper.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
--- /dev/null
+// 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;
+ }
+}
// 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;
+ }
+
MakeReadOnly();
_isInitializedForReflectionSerializer = true;
}
internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer;
private volatile bool _isInitializedForReflectionSerializer;
+ // Only populated in .NET 6 compatibility mode encoding reflection fallback in source gen
+ private IJsonTypeInfoResolver? _effectiveJsonTypeInfoResolver;
+
private JsonTypeInfo? GetTypeInfoNoCaching(Type type)
{
- JsonTypeInfo? info = _typeInfoResolver?.GetTypeInfo(type, this);
+ JsonTypeInfo? info = (_effectiveJsonTypeInfoResolver ?? _typeInfoResolver)?.GetTypeInfo(type, this);
if (info != null)
{
}
[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;
Assert.Throws<NotSupportedException>(() => context.Options.GetConverter(typeof(MyClass)));
Assert.Throws<NotSupportedException>(() => 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<InvalidOperationException>(() => 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<JsonConverter<MyClass>>(converter);
+
+ }, options).Dispose();
}
[Fact]