Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal sealed class CastingConverter<T> : JsonConverter<T>
public override bool HandleNull { get; }
internal override bool SupportsCreateObjectDelegate => _sourceConverter.SupportsCreateObjectDelegate;

internal CastingConverter(JsonConverter sourceConverter, bool handleNull, bool handleNullOnRead, bool handleNullOnWrite)
internal CastingConverter(JsonConverter sourceConverter)
{
Debug.Assert(typeof(T).IsInSubtypeRelationshipWith(sourceConverter.TypeToConvert));
Debug.Assert(sourceConverter.SourceConverterForCastingConverter is null, "casting converters should not be layered.");
Expand All @@ -31,9 +31,9 @@ internal CastingConverter(JsonConverter sourceConverter, bool handleNull, bool h
CanBePolymorphic = sourceConverter.CanBePolymorphic;

// Ensure HandleNull values reflect the exact configuration of the source converter
HandleNullOnRead = handleNullOnRead;
HandleNullOnWrite = handleNullOnWrite;
HandleNull = handleNull;
HandleNullOnRead = sourceConverter.HandleNullOnRead;
HandleNullOnWrite = sourceConverter.HandleNullOnWrite;
HandleNull = sourceConverter.HandleNullOnWrite;
}

internal override JsonConverter? SourceConverterForCastingConverter => _sourceConverter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,41 @@ internal virtual JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
throw new InvalidOperationException();
}

internal abstract JsonConverter<TTarget> CreateCastingConverter<TTarget>();
internal JsonConverter<TTarget> CreateCastingConverter<TTarget>()
{
Debug.Assert(this is not JsonConverterFactory);

if (this is JsonConverter<TTarget> conv)
{
return conv;
}
else
{
JsonSerializerOptions.CheckConverterNullabilityIsSameAsPropertyType(this, typeof(TTarget));

// Avoid layering casting converters by consulting any source converters directly.
return
SourceConverterForCastingConverter?.CreateCastingConverter<TTarget>()
?? new CastingConverter<TTarget>(this);
}
}

/// <summary>
/// Tracks whether the JsonConverter&lt;T&gt;.HandleNull property has been overridden by a derived converter.
/// </summary>
internal bool UsesDefaultHandleNull { get; private protected set; }

/// <summary>
/// Does the converter want to be called when reading null tokens.
/// When JsonConverter&lt;T&gt;.HandleNull isn't overridden this can still be true for non-nullable structs.
/// </summary>
internal bool HandleNullOnRead { get; private protected init; }

/// <summary>
/// Does the converter want to be called for null values.
/// Should always match the precise value of the JsonConverter&lt;T&gt;.HandleNull virtual property.
/// </summary>
internal bool HandleNullOnWrite { get; private protected init; }

/// <summary>
/// Set if this converter is itself a casting converter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,5 @@ internal sealed override void WriteNumberWithCustomHandlingAsObject(Utf8JsonWrit

throw new InvalidOperationException();
}

internal sealed override JsonConverter<TTarget> CreateCastingConverter<TTarget>()
{
ThrowHelper.ThrowInvalidOperationException_ConverterCanConvertMultipleTypes(typeof(TTarget), this);
return null!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ protected internal JsonConverter()
HandleNullOnRead = true;
HandleNullOnWrite = true;
}
else
else if (UsesDefaultHandleNull)
{
// For the HandleNull == false case, either:
// 1) The default values are assigned in this type's virtual HandleNull property
// or
// 2) A converter overrode HandleNull and returned false so HandleNullOnRead and HandleNullOnWrite
// will be their default values of false.
// If the type doesn't support null, allow the converter a chance to modify.
// These semantics are backwards compatible with 3.0.
HandleNullOnRead = default(T) is not null;

// The framework handles null automatically on writes.
HandleNullOnWrite = false;
}
}

Expand All @@ -56,21 +57,6 @@ internal sealed override JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions o
return new JsonTypeInfo<T>(this, options);
}

internal sealed override JsonConverter<TTarget> CreateCastingConverter<TTarget>()
{
if (this is JsonConverter<TTarget> conv)
{
return conv;
}

JsonSerializerOptions.CheckConverterNullabilityIsSameAsPropertyType(this, typeof(TTarget));

// Avoid layering casting converters by consulting any source converters directly.
return
SourceConverterForCastingConverter?.CreateCastingConverter<TTarget>()
?? new CastingConverter<TTarget>(this, handleNull: HandleNull, handleNullOnRead: HandleNullOnRead, handleNullOnWrite: HandleNullOnWrite);
}

internal override Type? KeyType => null;

internal override Type? ElementType => null;
Expand All @@ -86,31 +72,11 @@ public virtual bool HandleNull
{
get
{
// HandleNull is only called by the framework once during initialization and any
// subsequent calls elsewhere would just re-initialize to the same values (we don't
// track a "hasInitialized" flag since that isn't necessary).

// If the type doesn't support null, allow the converter a chance to modify.
// These semantics are backwards compatible with 3.0.
HandleNullOnRead = default(T) is not null;

// The framework handles null automatically on writes.
HandleNullOnWrite = false;

UsesDefaultHandleNull = true;
return false;
}
}

/// <summary>
/// Does the converter want to be called when reading null tokens.
/// </summary>
internal bool HandleNullOnRead { get; private protected set; }

/// <summary>
/// Does the converter want to be called for null values.
/// </summary>
internal bool HandleNullOnWrite { get; private protected set; }

// This non-generic API is sealed as it just forwards to the generic version.
internal sealed override void WriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)
{
Expand Down