diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index f08e3e8e57b4b0..507b75ed8b379f 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -581,9 +581,6 @@
The generic type of the converter for property '{0}.{1}' must match with the specified converter type '{2}'. The converter must not be 'null'.
-
- Built-in type converters have not been initialized. There is no converter available for type '{0}'. To root all built-in converters, use a 'JsonSerializerOptions'-based method of the 'JsonSerializer'.
-
Metadata for type '{0}' was not provided by TypeInfoResolver of type '{1}'. 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.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs
index 19a3d3c74c3487..f42adbc31fe409 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs
@@ -37,12 +37,12 @@ public override bool CanConvert(Type typeToConvert) =>
case FSharpKind.Option:
elementType = typeToConvert.GetGenericArguments()[0];
converterFactoryType = typeof(FSharpOptionConverter<,>).MakeGenericType(typeToConvert, elementType);
- constructorArguments = new object[] { options.GetConverterFromTypeInfo(elementType) };
+ constructorArguments = new object[] { options.GetConverterInternal(elementType) };
break;
case FSharpKind.ValueOption:
elementType = typeToConvert.GetGenericArguments()[0];
converterFactoryType = typeof(FSharpValueOptionConverter<,>).MakeGenericType(typeToConvert, elementType);
- constructorArguments = new object[] { options.GetConverterFromTypeInfo(elementType) };
+ constructorArguments = new object[] { options.GetConverterInternal(elementType) };
break;
case FSharpKind.List:
elementType = typeToConvert.GetGenericArguments()[0];
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs
index d077627720bda4..6c0a095061fac9 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs
@@ -86,7 +86,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, object? va
Debug.Assert(value != null);
Type runtimeType = value.GetType();
- JsonConverter runtimeConverter = options.GetConverterFromTypeInfo(runtimeType);
+ JsonConverter runtimeConverter = options.GetConverterInternal(runtimeType);
if (runtimeConverter == this)
{
ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType, this);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs
index 36f74f39bf8d02..cbc89a4fb7bba6 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs
@@ -22,7 +22,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
Type valueTypeToConvert = typeToConvert.GetGenericArguments()[0];
- JsonConverter valueConverter = options.GetConverterFromTypeInfo(valueTypeToConvert);
+ JsonConverter valueConverter = options.GetConverterInternal(valueTypeToConvert);
Debug.Assert(valueConverter != null);
// If the value type has an interface or object converter, just return that converter directly.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
index 631fdabc953a2d..e8ff50c9dbfabb 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
@@ -67,8 +67,6 @@ internal virtual void ReadElementAndSetProperty(
throw new InvalidOperationException(SR.NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty);
}
- internal abstract JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options);
-
internal abstract JsonParameterInfo CreateJsonParameterInfo();
internal abstract JsonConverter CreateCastingConverter();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
index 02f9cb940970b5..f9bcd6e7b4878a 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
@@ -32,13 +32,6 @@ protected JsonConverterFactory() { }
///
public abstract JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options);
- internal override JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options)
- {
- Debug.Fail("We should never get here.");
-
- throw new InvalidOperationException();
- }
-
internal override JsonParameterInfo CreateJsonParameterInfo()
{
Debug.Fail("We should never get here.");
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
index 0b7afd0c555f0a..da4963acdb823e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
@@ -69,14 +69,6 @@ public override bool CanConvert(Type typeToConvert)
internal override ConverterStrategy ConverterStrategy => ConverterStrategy.Value;
- internal sealed override JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options)
- {
- return new JsonPropertyInfo(declaringTypeInfo.Type, declaringTypeInfo, options)
- {
- DefaultConverterForType = this
- };
- }
-
internal sealed override JsonParameterInfo CreateJsonParameterInfo()
{
return new JsonParameterInfo();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
index c0809f187894d6..f372d8bd8336e7 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
@@ -45,44 +45,34 @@ public JsonTypeInfo GetTypeInfo(Type type)
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
}
- JsonTypeInfo? typeInfo;
- if (IsLockedInstance)
- {
- typeInfo = GetCachingContext()?.GetOrAddJsonTypeInfo(type);
- typeInfo?.EnsureConfigured();
- }
- else
- {
- typeInfo = GetTypeInfoNoCaching(type);
- }
-
- if (typeInfo is null)
- {
- ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver);
- }
-
- return typeInfo;
+ return GetTypeInfoInternal(type, resolveIfMutable: true);
}
///
- /// This method returns configured non-null JsonTypeInfo
+ /// Same as GetTypeInfo but without validation and additional knobs.
///
- internal JsonTypeInfo GetTypeInfoCached(Type type)
+ internal JsonTypeInfo GetTypeInfoInternal(Type type, bool ensureConfigured = true, bool resolveIfMutable = false)
{
JsonTypeInfo? typeInfo = null;
if (IsLockedInstance)
{
typeInfo = GetCachingContext()?.GetOrAddJsonTypeInfo(type);
+ if (ensureConfigured)
+ {
+ typeInfo?.EnsureConfigured();
+ }
+ }
+ else if (resolveIfMutable)
+ {
+ typeInfo = GetTypeInfoNoCaching(type);
}
if (typeInfo == null)
{
ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type, TypeInfoResolver);
- return null;
}
- typeInfo.EnsureConfigured();
return typeInfo;
}
@@ -108,7 +98,7 @@ internal JsonTypeInfo GetTypeInfoForRootType(Type type)
if (jsonTypeInfo?.Type != type)
{
- jsonTypeInfo = GetTypeInfoCached(type);
+ jsonTypeInfo = GetTypeInfoInternal(type);
_lastTypeInfo = jsonTypeInfo;
}
@@ -125,12 +115,7 @@ internal void ClearCaches()
{
Debug.Assert(IsLockedInstance);
- if (_cachingContext is null && _typeInfoResolver is not null)
- {
- _cachingContext = TrackedCachingContexts.GetOrCreate(this);
- }
-
- return _cachingContext;
+ return _cachingContext ??= TrackedCachingContexts.GetOrCreate(this);
}
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
index c28edbdaf3fa4e..33b0351655afa5 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Reflection;
using System.Text.Json.Serialization;
@@ -48,35 +47,21 @@ public JsonConverter GetConverter(Type typeToConvert)
if (_typeInfoResolver is null)
{
- // Backward compatibility -- root the default reflection converters
+ // Backward compatibility -- root & query the default reflection converters
// but do not populate the TypeInfoResolver setting.
- DefaultJsonTypeInfoResolver.RootDefaultInstance();
+ return DefaultJsonTypeInfoResolver.GetConverterForType(typeToConvert, this);
}
- return GetConverterFromTypeInfo(typeToConvert);
+ return GetConverterInternal(typeToConvert);
}
///
- /// Same as GetConverter but does not root converters
+ /// Same as GetConverter but without defaulting to reflection converters.
///
- internal JsonConverter GetConverterFromTypeInfo(Type typeToConvert)
+ internal JsonConverter GetConverterInternal(Type typeToConvert)
{
- JsonConverter? converter;
-
- if (IsLockedInstance)
- {
- converter = GetCachingContext()?.GetOrAddJsonTypeInfo(typeToConvert)?.Converter;
- }
- else
- {
- // We do not want to lock options instance here but we need to return correct answer
- // which means we need to go through TypeInfoResolver but without caching because that's the
- // only place which will have correct converter for JsonSerializerContext and reflection
- // based resolver. It will also work correctly for combined resolvers.
- converter = GetTypeInfoNoCaching(typeToConvert)?.Converter;
- }
-
- return converter ?? GetConverterFromListOrBuiltInConverter(typeToConvert);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfoInternal(typeToConvert, ensureConfigured: false, resolveIfMutable: true);
+ return jsonTypeInfo.Converter;
}
internal JsonConverter? GetConverterFromList(Type typeToConvert)
@@ -92,28 +77,6 @@ internal JsonConverter GetConverterFromTypeInfo(Type typeToConvert)
return null;
}
- internal JsonConverter GetConverterFromListOrBuiltInConverter(Type typeToConvert)
- {
- JsonConverter? converter = GetConverterFromList(typeToConvert);
- return GetCustomOrBuiltInConverter(typeToConvert, converter);
- }
-
- internal JsonConverter GetCustomOrBuiltInConverter(Type typeToConvert, JsonConverter? converter)
- {
- // Attempt to get built-in converter.
- converter ??= DefaultJsonTypeInfoResolver.GetBuiltInConverter(typeToConvert);
- // Expand potential convert converter factory.
- converter = ExpandConverterFactory(converter, typeToConvert);
-
- if (!converter.TypeToConvert.IsInSubtypeRelationshipWith(typeToConvert))
- {
- ThrowHelper.ThrowInvalidOperationException_SerializationConverterNotCompatible(converter.GetType(), converter.TypeToConvert);
- }
-
- CheckConverterNullabilityIsSameAsPropertyType(converter, typeToConvert);
- return converter;
- }
-
[return: NotNullIfNotNull("converter")]
internal JsonConverter? ExpandConverterFactory(JsonConverter? converter, Type typeToConvert)
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs
index 53e0a5a68bc017..4b48d7b2d72ae8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs
@@ -18,8 +18,8 @@ internal sealed class CustomJsonTypeInfo : JsonTypeInfo
///
/// Creates serialization metadata for a type using a simple converter.
///
- internal CustomJsonTypeInfo(JsonSerializerOptions options)
- : base(options.GetConverterFromListOrBuiltInConverter(typeof(T)), options)
+ internal CustomJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
+ : base(converter, options)
{
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs
index 690281c6e09496..152b84e9849310 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs
@@ -81,16 +81,10 @@ void Add(JsonConverter converter) =>
converters.Add(converter.TypeToConvert, converter);
}
- internal static JsonConverter GetBuiltInConverter(Type typeToConvert)
+ private static JsonConverter GetBuiltInConverter(Type typeToConvert)
{
- if (s_defaultSimpleConverters == null || s_defaultFactoryConverters == null)
- {
- // (De)serialization using serializer's options-based methods has not yet occurred, so the built-in converters are not rooted.
- // Even though source-gen code paths do not call this method , we do not root all the
- // built-in converters here since we fetch converters for any type included for source generation from the binded context (Priority 1).
- ThrowHelper.ThrowNotSupportedException_BuiltInConvertersNotRooted(typeToConvert);
- return null!;
- }
+ Debug.Assert(s_defaultSimpleConverters != null);
+ Debug.Assert(s_defaultFactoryConverters != null);
JsonConverter? converter;
if (s_defaultSimpleConverters.TryGetValue(typeToConvert, out converter))
@@ -99,11 +93,11 @@ internal static JsonConverter GetBuiltInConverter(Type typeToConvert)
}
else
{
- foreach (JsonConverter item in s_defaultFactoryConverters)
+ foreach (JsonConverterFactory factory in s_defaultFactoryConverters)
{
- if (item.CanConvert(typeToConvert))
+ if (factory.CanConvert(typeToConvert))
{
- converter = item;
+ converter = factory;
break;
}
}
@@ -125,39 +119,28 @@ internal static bool TryGetDefaultSimpleConverter(Type typeToConvert, [NotNullWh
return s_defaultSimpleConverters.TryGetValue(typeToConvert, out converter);
}
- // This method gets the runtime information for a given type or property.
- // The runtime information consists of the following:
- // - class type,
- // - element type (if the type is a collection),
- // - the converter (either native or custom), if one exists.
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- internal static JsonConverter GetConverterForMember(
- Type typeToConvert,
- MemberInfo memberInfo,
- JsonSerializerOptions options,
- out JsonConverter? customConverter)
+ internal static JsonConverter? GetCustomConverterForMember(Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options)
{
Debug.Assert(memberInfo is FieldInfo or PropertyInfo);
Debug.Assert(typeToConvert != null);
JsonConverterAttribute? converterAttribute = memberInfo.GetUniqueCustomAttribute(inherit: false);
- customConverter = converterAttribute is null ? null : GetConverterFromAttribute(converterAttribute, typeToConvert, memberInfo, options);
-
- return options.TryGetTypeInfoCached(typeToConvert, out JsonTypeInfo? typeInfo)
- ? typeInfo.Converter
- : GetConverterForType(typeToConvert, options);
+ return converterAttribute is null ? null : GetConverterFromAttribute(converterAttribute, typeToConvert, memberInfo, options);
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- internal static JsonConverter GetConverterForType(Type typeToConvert, JsonSerializerOptions options)
+ internal static JsonConverter GetConverterForType(Type typeToConvert, JsonSerializerOptions options, bool resolveJsonConverterAttribute = true)
{
+ RootDefaultInstance(); // Ensure default converters are rooted.
+
// Priority 1: Attempt to get custom converter from the Converters list.
JsonConverter? converter = options.GetConverterFromList(typeToConvert);
// Priority 2: Attempt to get converter from [JsonConverter] on the type being converted.
- if (converter == null)
+ if (resolveJsonConverterAttribute && converter == null)
{
JsonConverterAttribute? converterAttribute = typeToConvert.GetUniqueCustomAttribute(inherit: false);
if (converterAttribute != null)
@@ -166,8 +149,18 @@ internal static JsonConverter GetConverterForType(Type typeToConvert, JsonSerial
}
}
- // Priority 3: Fall back to built-in converters and validate result
- return options.GetCustomOrBuiltInConverter(typeToConvert, converter);
+ // Priority 3: Query the built-in converters.
+ converter ??= GetBuiltInConverter(typeToConvert);
+
+ // Expand if factory converter & validate.
+ converter = options.ExpandConverterFactory(converter, typeToConvert);
+ if (!converter.TypeToConvert.IsInSubtypeRelationshipWith(typeToConvert))
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializationConverterNotCompatible(converter.GetType(), converter.TypeToConvert);
+ }
+
+ JsonSerializerOptions.CheckConverterNullabilityIsSameAsPropertyType(converter, typeToConvert);
+ return converter;
}
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs
index 4b4990818e1ce2..44ea6c1b06fa01 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs
@@ -90,7 +90,11 @@ private JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- private static JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options) => new ReflectionJsonTypeInfo(options);
+ private static JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options)
+ {
+ JsonConverter converter = GetConverterForType(typeof(T), options);
+ return new ReflectionJsonTypeInfo(converter, options);
+ }
///
/// List of JsonTypeInfo modifiers. Modifying callbacks are called consecutively after initial resolution
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs
index 879c1884a8a898..9bc156074bddcd 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs
@@ -283,7 +283,7 @@ public static JsonConverter GetEnumConverter(JsonSerializerOptions options
ThrowHelper.ThrowArgumentNullException(nameof(options));
}
- JsonConverter underlyingConverter = GetTypedConverter(options.GetConverterFromTypeInfo(typeof(T)));
+ JsonConverter underlyingConverter = GetTypedConverter(options.GetConverterInternal(typeof(T)));
return new NullableConverter(underlyingConverter);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
index e5342b24c0791c..247f58db58cdc3 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
@@ -39,7 +39,7 @@ public JsonTypeInfo JsonTypeInfo
{
Debug.Assert(Options != null);
Debug.Assert(ShouldDeserialize);
- return _jsonTypeInfo ??= Options.GetTypeInfoCached(PropertyType);
+ return _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType);
}
set
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
index 808698953a95f4..1418afb476a221 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
@@ -22,23 +22,6 @@ public abstract class JsonPropertyInfo
internal ConverterStrategy ConverterStrategy { get; private protected set; }
- ///
- /// Converter resolved from PropertyType and not taking in consideration any custom attributes or custom settings.
- /// - for reflection we store the original value since we need it in order to construct typed JsonPropertyInfo
- /// - for source gen it remains null, we will initialize it only if someone used resolver to remove CustomConverter
- ///
- internal JsonConverter? DefaultConverterForType
- {
- get => _defaultConverterForType;
- set
- {
- _defaultConverterForType = value;
- ConverterStrategy = value?.ConverterStrategy ?? default;
- }
- }
-
- private JsonConverter? _defaultConverterForType;
-
///
/// Converter after applying CustomConverter (i.e. JsonConverterAttribute)
///
@@ -255,7 +238,8 @@ internal void Configure()
CacheNameAsUtf8BytesAndEscapedNameSection();
}
- DetermineEffectiveConverter();
+ _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
+ DetermineEffectiveConverter(_jsonTypeInfo);
if (IsForTypeInfo)
{
@@ -269,7 +253,7 @@ internal void Configure()
}
}
- private protected abstract void DetermineEffectiveConverter();
+ private protected abstract void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo);
private protected abstract void DetermineMemberAccessors(MemberInfo memberInfo);
private void DeterminePoliciesFromMember(MemberInfo memberInfo)
@@ -406,12 +390,15 @@ private void DetermineNumberHandlingForTypeInfo()
private void DetermineNumberHandlingForProperty()
{
+ Debug.Assert(!IsConfigured, "Should not be called post-configuration.");
+ Debug.Assert(_jsonTypeInfo != null, "Must have already been determined on configuration.");
+
bool numberHandlingIsApplicable = NumberHandingIsApplicable();
if (numberHandlingIsApplicable)
{
// Priority 1: Get handling from attribute on property/field, its parent class type or property type.
- JsonNumberHandling? handling = NumberHandling ?? DeclaringTypeNumberHandling ?? JsonTypeInfo.NumberHandling;
+ JsonNumberHandling? handling = NumberHandling ?? DeclaringTypeNumberHandling ?? _jsonTypeInfo.NumberHandling;
// Priority 2: Get handling from JsonSerializerOptions instance.
if (!handling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
@@ -563,7 +550,11 @@ internal bool IgnoreReadOnlyMember
///
public string Name
{
- get => _name;
+ get
+ {
+ Debug.Assert(_name != null);
+ return _name;
+ }
set
{
VerifyMutable();
@@ -577,7 +568,7 @@ public string Name
}
}
- private string _name = null!;
+ private string? _name;
///
/// Utf8 version of Name.
@@ -664,7 +655,7 @@ JsonConverter GetDictionaryValueConverter(Type dictionaryValueType)
// Slower path for non-generic types that implement IDictionary<,>.
// It is possible to cache this converter on JsonTypeInfo if we assume the property value
// will always be the same type for all instances.
- converter = Options.GetConverterFromTypeInfo(dictionaryValueType);
+ converter = Options.GetConverterInternal(dictionaryValueType);
}
Debug.Assert(converter != null);
@@ -686,7 +677,7 @@ internal bool ReadJsonExtensionDataValue(ref ReadStack state, ref Utf8JsonReader
return true;
}
- JsonConverter converter = (JsonConverter)Options.GetConverterFromTypeInfo(typeof(JsonElement));
+ JsonConverter converter = (JsonConverter)Options.GetConverterInternal(typeof(JsonElement));
if (!converter.TryRead(ref reader, typeof(JsonElement), Options, ref state, out JsonElement jsonElement))
{
// JsonElement is a struct that must be read in full.
@@ -717,26 +708,17 @@ internal JsonTypeInfo JsonTypeInfo
{
get
{
- if (_jsonTypeInfo != null)
- {
- // We should not call it on set as it's usually called during initialization
- // which is too early to `lock` the JsonTypeInfo
- // If this property ever becomes public we should move this to callsites
- _jsonTypeInfo.EnsureConfigured();
- }
- else
- {
- // GetOrAddJsonTypeInfo already ensures it's configured.
- _jsonTypeInfo = Options.GetTypeInfoCached(PropertyType);
- }
-
+ Debug.Assert(IsConfigured);
+ Debug.Assert(_jsonTypeInfo != null);
+ _jsonTypeInfo.EnsureConfigured();
return _jsonTypeInfo;
}
set
{
- // Used by JsonMetadataServices.
// This could potentially be double initialized
Debug.Assert(_jsonTypeInfo == null || _jsonTypeInfo == value);
+ // Ensure the right strategy is surfaced in PropertyInfoForTypeInfo early
+ ConverterStrategy = value?.Converter.ConverterStrategy ?? default;
_jsonTypeInfo = value;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
index 6d6c4824321f81..31d0e699ef78d8 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
@@ -188,7 +188,6 @@ internal JsonPropertyInfo(JsonPropertyInfoValues propertyInfo, JsonSerializer
SrcGen_IsPublic = propertyInfo.IsPublic;
SrcGen_HasJsonInclude = propertyInfo.HasJsonInclude;
IsExtensionData = propertyInfo.IsExtensionData;
- DefaultConverterForType = propertyInfo.PropertyTypeInfo?.Converter as JsonConverter;
CustomConverter = propertyInfo.Converter;
if (propertyInfo.IgnoreCondition != JsonIgnoreCondition.Always)
@@ -202,12 +201,11 @@ internal JsonPropertyInfo(JsonPropertyInfoValues propertyInfo, JsonSerializer
NumberHandling = propertyInfo.NumberHandling;
}
- private protected override void DetermineEffectiveConverter()
+ private protected override void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo)
{
JsonConverter converter =
Options.ExpandConverterFactory(CustomConverter, PropertyType)
- ?? DefaultConverterForType
- ?? Options.GetConverterFromTypeInfo(PropertyType);
+ ?? jsonTypeInfo.Converter;
TypedEffectiveConverter = converter is JsonConverter typedConv ? typedConv : converter.CreateCastingConverter();
ConverterStrategy = TypedEffectiveConverter.ConverterStrategy;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
index 9e97d59d73b0ba..eb991681847a7a 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
@@ -54,22 +54,35 @@ public abstract partial class JsonTypeInfo
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
- internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType, JsonConverter converter)
+ internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType)
{
- Debug.Assert(propertyType.IsInSubtypeRelationshipWith(converter.TypeToConvert));
+ JsonPropertyInfo? jsonPropertyInfo;
- if (converter.TypeToConvert == propertyType)
+ if (Options.TryGetTypeInfoCached(propertyType, out JsonTypeInfo? jsonTypeInfo))
{
- // For the vast majority of use cases, the converter type matches the property type.
- // Avoid reflection-based initialization by delegating JsonPropertyInfo construction to the converter itself.
- return converter.CreateJsonPropertyInfo(declaringTypeInfo: this, Options);
+ // If a JsonTypeInfo has already been cached for the property type,
+ // avoid reflection-based initialization by delegating construction
+ // of JsonPropertyInfo construction to the property type metadata.
+ return jsonTypeInfo.CreateJsonPropertyInfo(declaringTypeInfo: this, Options);
+ }
+ else
+ {
+ // Metadata for `propertyType` has not been registered yet.
+ // Use reflection to instantiate the correct JsonPropertyInfo
+ s_createJsonPropertyInfo ??= typeof(JsonTypeInfo).GetMethod(nameof(CreateJsonPropertyInfo), BindingFlags.NonPublic | BindingFlags.Static)!;
+ MethodInfo factoryInfo = s_createJsonPropertyInfo.MakeGenericMethod(propertyType);
+ jsonPropertyInfo = (JsonPropertyInfo)factoryInfo.Invoke(null, new object[] { this, Options })!;
}
- s_createJsonPropertyInfo ??= typeof(JsonTypeInfo).GetMethod(nameof(CreateJsonPropertyInfo), BindingFlags.NonPublic | BindingFlags.Static)!;
- MethodInfo factoryInfo = s_createJsonPropertyInfo.MakeGenericMethod(propertyType);
- return (JsonPropertyInfo)factoryInfo.Invoke(null, new object[] { this, Options })!;
+ Debug.Assert(jsonPropertyInfo.PropertyType == propertyType);
+ return jsonPropertyInfo;
}
+ ///
+ /// Creates a JsonPropertyInfo whose property type matches the type of this JsonTypeInfo instance.
+ ///
+ private protected abstract JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options);
+
private static JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options)
=> new JsonPropertyInfo(declaringTypeInfo.Type, declaringTypeInfo, options);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
index b08e40a5af7318..056a9b2cf6844b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
@@ -226,7 +226,7 @@ internal JsonTypeInfo? ElementTypeInfo
{
// GetOrAddJsonTypeInfo already ensures JsonTypeInfo is configured
// also see comment on JsonPropertyInfo.JsonTypeInfo
- _elementTypeInfo = Options.GetTypeInfoCached(ElementType);
+ _elementTypeInfo = Options.GetTypeInfoInternal(ElementType);
}
}
else
@@ -268,7 +268,7 @@ internal JsonTypeInfo? KeyTypeInfo
// GetOrAddJsonTypeInfo already ensures JsonTypeInfo is configured
// also see comment on JsonPropertyInfo.JsonTypeInfo
- _keyTypeInfo = Options.GetTypeInfoCached(KeyType);
+ _keyTypeInfo = Options.GetTypeInfoInternal(KeyType);
}
}
else
@@ -508,11 +508,22 @@ internal string GetDebugInfo()
internal virtual void LateAddProperties() { }
///
- /// Creates JsonTypeInfo
+ /// Creates a blank instance.
///
- /// Type for which JsonTypeInfo stores metadata for
- /// Options associated with JsonTypeInfo
- /// JsonTypeInfo instance
+ /// The type for which contract metadata is specified.
+ /// The instance the metadata is associated with.
+ /// A blank instance.
+ /// is null.
+ ///
+ /// The returned will be blank, with the exception of the
+ /// property which will be resolved either from
+ /// or the built-in converters for the type.
+ /// Any converters specified via on the type declaration
+ /// will not be resolved by this method.
+ ///
+ /// What converter does get resolved influences the value of ,
+ /// which constrains the type of metadata that can be modified in the instance.
+ ///
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public static JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
@@ -522,18 +533,30 @@ public static JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions option
ThrowHelper.ThrowArgumentNullException(nameof(options));
}
- DefaultJsonTypeInfoResolver.RootDefaultInstance();
- return new CustomJsonTypeInfo(options);
+ JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(typeof(T), options, resolveJsonConverterAttribute: false);
+ return new CustomJsonTypeInfo(converter, options);
}
private static MethodInfo? s_createJsonTypeInfo;
///
- /// Creates JsonTypeInfo
+ /// Creates a blank instance.
///
- /// Type for which JsonTypeInfo stores metadata for
- /// Options associated with JsonTypeInfo
- /// JsonTypeInfo instance
+ /// The type for which contract metadata is specified.
+ /// The instance the metadata is associated with.
+ /// A blank instance.
+ /// or is null.
+ /// cannot be used for serialization.
+ ///
+ /// The returned will be blank, with the exception of the
+ /// property which will be resolved either from
+ /// or the built-in converters for the type.
+ /// Any converters specified via on the type declaration
+ /// will not be resolved by this method.
+ ///
+ /// What converter does get resolved influences the value of ,
+ /// which constrains the type of metadata that can be modified in the instance.
+ ///
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options)
@@ -553,18 +576,19 @@ public static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions o
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
}
- DefaultJsonTypeInfoResolver.RootDefaultInstance();
s_createJsonTypeInfo ??= typeof(JsonTypeInfo).GetMethod(nameof(CreateJsonTypeInfo), new Type[] { typeof(JsonSerializerOptions) })!;
return (JsonTypeInfo)s_createJsonTypeInfo.MakeGenericMethod(type)
.Invoke(null, new object[] { options })!;
}
///
- /// Creates JsonPropertyInfo
+ /// Creates a blank instance for the current .
///
- /// Type of the property
- /// Name of the property
- /// JsonPropertyInfo instance
+ /// The declared type for the property.
+ /// The property name used in JSON serialization and deserialization.
+ /// A blank instance.
+ /// or is null.
+ /// cannot be used for serialization.
[RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
[RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
@@ -584,8 +608,7 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(propertyType), propertyType, Type, name);
}
- JsonConverter converter = Options.GetConverterFromListOrBuiltInConverter(propertyType);
- JsonPropertyInfo propertyInfo = CreatePropertyUsingReflection(propertyType, converter);
+ JsonPropertyInfo propertyInfo = CreatePropertyUsingReflection(propertyType);
propertyInfo.Name = name;
return propertyInfo;
@@ -885,7 +908,7 @@ internal static bool IsValidExtensionDataProperty(JsonPropertyInfo jsonPropertyI
// Avoid a reference to typeof(JsonNode) to support trimming.
(memberType.FullName == JsonObjectTypeName && ReferenceEquals(memberType.Assembly, typeof(JsonTypeInfo).Assembly));
- return typeIsValid && jsonPropertyInfo.Options.GetConverterFromTypeInfo(memberType) != null;
+ return typeIsValid;
}
internal JsonPropertyDictionary CreatePropertyCache(int capacity)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
index 175efb0f601434..0b069b4145b3e4 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
@@ -96,12 +96,19 @@ private protected override JsonPropertyInfo CreatePropertyInfoForTypeInfo()
declaringTypeInfo: null,
Options)
{
- DefaultConverterForType = Converter,
JsonTypeInfo = this,
IsForTypeInfo = true,
};
}
+ private protected override JsonPropertyInfo CreateJsonPropertyInfo(JsonTypeInfo declaringTypeInfo, JsonSerializerOptions options)
+ {
+ return new JsonPropertyInfo(declaringTypeInfo.Type, declaringTypeInfo, options)
+ {
+ JsonTypeInfo = this
+ };
+ }
+
private protected void MapInterfaceTypesToCallbacks()
{
// Callbacks currently only supported in object kinds
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs
index b85200a2ca1438..11f988190785f1 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PolymorphicTypeResolver.cs
@@ -249,7 +249,7 @@ public DerivedJsonTypeInfo(Type type, object? typeDiscriminator)
public Type DerivedType { get; }
public object? TypeDiscriminator { get; }
public JsonTypeInfo GetJsonTypeInfo(JsonSerializerOptions options)
- => _jsonTypeInfo ??= options.GetTypeInfoCached(DerivedType);
+ => _jsonTypeInfo ??= options.GetTypeInfoInternal(DerivedType);
}
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
index f7b91f3127fc15..224d0c2b4616c0 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
@@ -15,13 +15,6 @@ namespace System.Text.Json.Serialization.Metadata
///
internal sealed class ReflectionJsonTypeInfo : JsonTypeInfo
{
- [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
- [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- internal ReflectionJsonTypeInfo(JsonSerializerOptions options)
- : this(DefaultJsonTypeInfoResolver.GetConverterForType(typeof(T), options), options)
- {
- }
-
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
@@ -160,7 +153,7 @@ private void CacheMember(
ref bool propertyOrderSpecified,
ref Dictionary? ignoredMembers)
{
- JsonPropertyInfo? jsonPropertyInfo = AddProperty(typeToConvert, memberInfo, Options);
+ JsonPropertyInfo? jsonPropertyInfo = CreateProperty(typeToConvert, memberInfo, Options);
if (jsonPropertyInfo == null)
{
// ignored invalid property
@@ -177,7 +170,7 @@ private void CacheMember(
Justification = "The ctor is marked as RequiresUnreferencedCode")]
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "The ctor is marked RequiresDynamicCode.")]
- private JsonPropertyInfo? AddProperty(
+ private JsonPropertyInfo? CreateProperty(
Type typeToConvert,
MemberInfo memberInfo,
JsonSerializerOptions options)
@@ -192,23 +185,19 @@ private void CacheMember(
ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(typeToConvert, memberInfo.DeclaringType, memberInfo);
}
+ // Resolve any custom converters on the attribute level.
JsonConverter? customConverter;
- JsonConverter converter;
-
try
{
- converter = DefaultJsonTypeInfoResolver.GetConverterForMember(
- typeToConvert,
- memberInfo,
- options,
- out customConverter);
+ customConverter = DefaultJsonTypeInfoResolver.GetCustomConverterForMember(typeToConvert, memberInfo, options);
}
catch (InvalidOperationException) when (ignoreCondition == JsonIgnoreCondition.Always)
{
+ // skip property altogether if attribute is invalid and the property is ignored
return null;
}
- JsonPropertyInfo jsonPropertyInfo = CreatePropertyUsingReflection(typeToConvert, converter);
+ JsonPropertyInfo jsonPropertyInfo = CreatePropertyUsingReflection(typeToConvert);
jsonPropertyInfo.InitializeUsingMemberReflection(memberInfo, customConverter, ignoreCondition);
return jsonPropertyInfo;
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
index 705fe03d7f5d59..2d9aaabc2d46fd 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
@@ -126,7 +126,7 @@ public JsonConverter InitializePolymorphicReEntry(Type runtimeType, JsonSerializ
// if the current element is the same type as the previous element.
if (PolymorphicJsonTypeInfo?.PropertyType != runtimeType)
{
- JsonTypeInfo typeInfo = options.GetTypeInfoCached(runtimeType);
+ JsonTypeInfo typeInfo = options.GetTypeInfoInternal(runtimeType);
PolymorphicJsonTypeInfo = typeInfo.PropertyInfoForTypeInfo;
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
index 508ff39e205640..d5d9b77787a205 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
@@ -644,12 +644,6 @@ internal static void ThrowUnexpectedMetadataException(
}
}
- [DoesNotReturn]
- public static void ThrowNotSupportedException_BuiltInConvertersNotRooted(Type type)
- {
- throw new NotSupportedException(SR.Format(SR.BuiltInConvertersNotRooted, type));
- }
-
[DoesNotReturn]
public static void ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver? resolver)
{
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs b/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
index 0d78940904b52c..d9ac41cbd2b41d 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
@@ -49,3 +49,30 @@ let ``Support F# struct record serialization``() =
let ``Support F# struct record deserialization``() =
let result = JsonSerializer.Deserialize(MyStructRecord.ExpectedJson)
Assert.Equal(MyStructRecord.Value, result)
+
+type RecursiveRecord =
+ {
+ Next : RecursiveRecord option
+ }
+
+[]
+let ``Recursive records are supported``() =
+ let value = { Next = Some { Next = None } }
+ let json = JsonSerializer.Serialize(value)
+ Assert.Equal("""{"Next":{"Next":null}}""", json)
+ let deserializedValue = JsonSerializer.Deserialize(json)
+ Assert.Equal(value, deserializedValue)
+
+[]
+type RecursiveStructRecord =
+ {
+ Next : RecursiveStructRecord voption []
+ }
+
+[]
+let ``Recursive struct records are supported``() =
+ let value = { Next = [| ValueSome { Next = [| ValueNone |] } |] }
+ let json = JsonSerializer.Serialize(value)
+ Assert.Equal("""{"Next":[{"Next":[null]}]}""", json)
+ let deserializedValue = JsonSerializer.Deserialize(json)
+ Assert.Equal(value, deserializedValue)
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NullableTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NullableTests.cs
index a30e05ecf81bfd..c3888c8dc96757 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NullableTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/NullableTests.cs
@@ -439,5 +439,34 @@ public struct Person
public int? Age { get; set; }
public DateTime? Birthday { get; set; }
}
+
+ [Fact]
+ public static void RecursiveNullableStruct_Roundtrip()
+ {
+ var value = new RecursiveNullableStruct
+ {
+ Next = new RecursiveNullableStruct?[]
+ {
+ new()
+ {
+ Next = new RecursiveNullableStruct?[]
+ {
+ null
+ }
+ }
+ }
+ };
+
+ string json = JsonSerializer.Serialize(value);
+ Assert.Equal("""{"Next":[{"Next":[null]}]}""", json);
+ value = JsonSerializer.Deserialize(json);
+ string roundtripJson = JsonSerializer.Serialize(value);
+ Assert.Equal(roundtripJson, json);
+ }
+
+ public struct RecursiveNullableStruct
+ {
+ public RecursiveNullableStruct?[] Next { get; set; }
+ }
}
}
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 b1e6f2bac9b796..83ed3e65cf466a 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
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
@@ -9,7 +8,6 @@
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Tests;
using System.Text.Unicode;
-using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;
@@ -440,7 +438,7 @@ public static void Options_JsonSerializerContext_DoesNotFallbackToReflection()
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
- public static void Options_JsonSerializerContext_GetConverter_FallsBackToReflectionConverter()
+ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter()
{
RemoteExecutor.Invoke(static () =>
{
@@ -450,13 +448,17 @@ public static void Options_JsonSerializerContext_GetConverter_FallsBackToReflect
// Default converters have not been rooted yet
Assert.Null(context.GetTypeInfo(typeof(MyClass)));
Assert.Throws(() => context.Options.GetConverter(typeof(MyClass)));
-
- // Root converters process-wide by calling a Serialize overload accepting options
Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, context.Options));
- // We still can't resolve metadata for MyClass, but we can now resolve a converter using the rooted converters.
+ // Root converters process-wide using a default options instance
+ var options = new JsonSerializerOptions();
+ JsonConverter converter = options.GetConverter(typeof(MyClass));
+ Assert.IsAssignableFrom>(converter);
+
+ // We still can't resolve metadata for MyClass or get a converter using the rooted converters.
Assert.Null(context.GetTypeInfo(typeof(MyClass)));
- Assert.IsAssignableFrom>(context.Options.GetConverter(typeof(MyClass)));
+ Assert.Throws(() => context.Options.GetConverter(typeof(MyClass)));
+ Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, context.Options));
}).Dispose();
}
@@ -557,6 +559,23 @@ public static void Options_GetConverter_GivesCorrectCustomConverterAndReadWriteS
GenericConverterTestHelper("LongArrayConverter", new long[] { 1, 2, 3, 4 }, "\"1,2,3,4\"", options);
}
+ [Theory]
+ [InlineData(typeof(int))]
+ [InlineData(typeof(string))]
+ [InlineData(typeof(int[]))]
+ [InlineData(typeof(Poco))]
+ [InlineData(typeof(Dictionary))]
+ public static void Options_GetConverter_CustomResolver_DoesNotReturnConverterForUnsupportedType(Type type)
+ {
+ var options = new JsonSerializerOptions { TypeInfoResolver = new NullResolver() };
+ Assert.Throws(() => options.GetConverter(type));
+ }
+
+ public class NullResolver : IJsonTypeInfoResolver
+ {
+ public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => null;
+ }
+
private static void GenericConverterTestHelper(string converterName, object objectValue, string stringValue, JsonSerializerOptions options, bool nullOptionOkay = true)
{
JsonConverter converter = (JsonConverter)options.GetConverter(typeof(T));