Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace System.Text.Json
{
internal static class AppContextSwitchHelper
{
private const string SourceGenReflectionFallbackEnabled_SwitchName = "System.Text.Json.Serialization.EnableSourceGenReflectionFallback";
private static int s_SourceGenReflectionFallbackEnabled_CachedValue;

public static bool IsSourceGenReflectionFallbackEnabled
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetCachedSwitchValue(SourceGenReflectionFallbackEnabled_SwitchName, ref s_SourceGenReflectionFallbackEnabled_CachedValue, defaultValue: false);
}

// Returns value of given switch using provided cache.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedSwitchValue(string switchName, ref int cachedSwitchValue, bool defaultValue)
{
// The cached switch value has 3 states: 0 - unknown, 1 - true, -1 - false
if (cachedSwitchValue < 0) return false;
if (cachedSwitchValue > 0) return true;

return GetCachedSwitchValueInternal(switchName, ref cachedSwitchValue, defaultValue);
}

private static bool GetCachedSwitchValueInternal(string switchName, ref int cachedSwitchValue, bool defaultValue)
{
if (!AppContext.TryGetSwitch(switchName, out bool isSwitchEnabled))
{
isSwitchEnabled = defaultValue;
}

cachedSwitchValue = isSwitchEnabled ? 1 /*true*/ : -1 /*false*/;
return isSwitchEnabled;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -643,17 +643,31 @@ 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;
}

MakeReadOnly();
_isInitializedForReflectionSerializer = true;
}

internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer;
private volatile bool _isInitializedForReflectionSerializer;
private IJsonTypeInfoResolver? _effectiveJsonTypeInfoResolver;

private JsonTypeInfo? GetTypeInfoNoCaching(Type type)
{
JsonTypeInfo? info = _typeInfoResolver?.GetTypeInfo(type, this);
JsonTypeInfo? info = (_effectiveJsonTypeInfoResolver ?? _typeInfoResolver)?.GetTypeInfo(type, this);

if (info != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,11 +475,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)
{
RemoteExecutor.Invoke(static () =>
RemoteExecutor.Invoke(static (string isCompatibilitySwitchExplicitlyDisabled) =>
{
if (bool.Parse(isCompatibilitySwitchExplicitlyDisabled))
{
AppContext.SetSwitch("System.Text.Json.Serialization.EnableSourceGenReflectionFallback", isEnabled: false);
}

JsonContext context = JsonContext.Default;
var unsupportedValue = new MyClass();

Expand All @@ -498,6 +505,33 @@ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToR
Assert.Throws<NotSupportedException>(() => context.Options.GetConverter(typeof(MyClass)));
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(unsupportedValue, context.Options));

}, isCompatibilitySwitchExplicitlyDisabled.ToString()).Dispose();
}

[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public static void Options_JsonSerializerContext_Net6CompatibilitySwitch_FallsBackToReflectionResolver()
{
RemoteExecutor.Invoke(static () =>
{
AppContext.SetSwitch("System.Text.Json.Serialization.EnableSourceGenReflectionFallback", isEnabled: true);

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);

}).Dispose();
}

Expand Down