Skip to content

Commit 859d08a

Browse files
Fix JsonSerializer eagerly resolving JsonTypeInfo for types with custom converters
Modified JsonPropertyInfo.Configure() to avoid eagerly resolving JsonTypeInfo when a custom converter is present, and updated JsonTypeInfo.Configure() to skip element/key type resolution for custom converters. Co-authored-by: eiriktsarpalis <[email protected]>
1 parent 28215d2 commit 859d08a

File tree

3 files changed

+52
-18
lines changed

3 files changed

+52
-18
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,24 @@ internal void Configure()
427427
}
428428
else
429429
{
430-
_jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType);
431-
_jsonTypeInfo.EnsureConfigured();
430+
// Try to expand any custom converter first to avoid
431+
// eagerly resolving JsonTypeInfo for types that have custom converters.
432+
// This prevents failures for types with properties that cannot be serialized
433+
// (e.g., ref properties) when they have a custom converter that handles serialization.
434+
JsonConverter? expandedCustomConverter = null;
435+
if (CustomConverter is not null)
436+
{
437+
expandedCustomConverter = Options.ExpandConverterFactory(CustomConverter, PropertyType);
438+
}
439+
440+
if (expandedCustomConverter is null)
441+
{
442+
// No custom converter or the factory returned null, so we need to get the JsonTypeInfo
443+
_jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType);
444+
_jsonTypeInfo.EnsureConfigured();
445+
}
432446

433-
DetermineEffectiveConverter(_jsonTypeInfo);
447+
DetermineEffectiveConverter(_jsonTypeInfo, expandedCustomConverter);
434448
DetermineNumberHandlingForProperty();
435449
DetermineEffectiveObjectCreationHandlingForProperty();
436450
DetermineSerializationCapabilities();
@@ -466,7 +480,7 @@ internal void Configure()
466480
IsConfigured = true;
467481
}
468482

469-
private protected abstract void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo);
483+
private protected abstract void DetermineEffectiveConverter(JsonTypeInfo? jsonTypeInfo, JsonConverter? expandedCustomConverter);
470484

471485
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
472486
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,29 @@ internal override void AddJsonParameterInfo(JsonParameterInfoValues parameterInf
144144
internal override void DetermineReflectionPropertyAccessors(MemberInfo memberInfo, bool useNonPublicAccessors)
145145
=> DefaultJsonTypeInfoResolver.DeterminePropertyAccessors<T>(this, memberInfo, useNonPublicAccessors);
146146

147-
private protected override void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo)
147+
private protected override void DetermineEffectiveConverter(JsonTypeInfo? jsonTypeInfo, JsonConverter? expandedCustomConverter)
148148
{
149-
Debug.Assert(jsonTypeInfo is JsonTypeInfo<T>);
149+
Debug.Assert(jsonTypeInfo is null or JsonTypeInfo<T>);
150150

151-
JsonConverter<T> converter =
152-
Options.ExpandConverterFactory(CustomConverter, PropertyType) // Expand any property-level custom converters.
153-
?.CreateCastingConverter<T>() // Cast to JsonConverter<T>, potentially with wrapping.
154-
?? ((JsonTypeInfo<T>)jsonTypeInfo).EffectiveConverter; // Fall back to the effective converter for the type.
151+
JsonConverter<T> converter;
152+
if (expandedCustomConverter is not null)
153+
{
154+
// Use the already-expanded custom converter
155+
converter = expandedCustomConverter.CreateCastingConverter<T>();
156+
}
157+
else
158+
{
159+
// Need to get JsonTypeInfo if we don't have it yet
160+
if (jsonTypeInfo is null)
161+
{
162+
jsonTypeInfo = Options.GetTypeInfoInternal(PropertyType);
163+
jsonTypeInfo.EnsureConfigured();
164+
JsonTypeInfo = jsonTypeInfo;
165+
}
166+
167+
// Fall back to the effective converter for the type
168+
converter = ((JsonTypeInfo<T>)jsonTypeInfo).EffectiveConverter;
169+
}
155170

156171
_effectiveConverter = converter;
157172
_typedEffectiveConverter = converter;

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -763,16 +763,21 @@ private void Configure()
763763
}
764764
}
765765

766-
if (ElementType != null)
766+
// Only resolve element and key types if using built-in converters.
767+
// Custom converters should handle their element/key types themselves.
768+
if (Converter.IsInternalConverter)
767769
{
768-
_elementTypeInfo ??= Options.GetTypeInfoInternal(ElementType);
769-
_elementTypeInfo.EnsureConfigured();
770-
}
770+
if (ElementType != null)
771+
{
772+
_elementTypeInfo ??= Options.GetTypeInfoInternal(ElementType);
773+
_elementTypeInfo.EnsureConfigured();
774+
}
771775

772-
if (KeyType != null)
773-
{
774-
_keyTypeInfo ??= Options.GetTypeInfoInternal(KeyType);
775-
_keyTypeInfo.EnsureConfigured();
776+
if (KeyType != null)
777+
{
778+
_keyTypeInfo ??= Options.GetTypeInfoInternal(KeyType);
779+
_keyTypeInfo.EnsureConfigured();
780+
}
776781
}
777782

778783
DetermineIsCompatibleWithCurrentOptions();

0 commit comments

Comments
 (0)